Loading [MathJax]/extensions/tex2jax.js
PISM, A Parallel Ice Sheet Model 2.2.2-d6b3a29ca committed by Constantine Khrulev on 2025-03-28
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
grounded_cell_fraction.cc
Go to the documentation of this file.
1/* Copyright (C) 2018, 2020, 2021, 2022, 2023 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 <cassert>
21#include <cmath> // fabs, isnan
22
23#include "pism/geometry/grounded_cell_fraction.hh"
24
25#include "pism/util/error_handling.hh"
26#include "pism/util/pism_utilities.hh" // clip
27#include "pism/util/array/Scalar.hh"
28
29namespace pism {
30
31struct Point {
32 double x;
33 double y;
34};
35
36/*!
37 * Consider the right triangle A(0,0) - B(1,0) - C(0,1).
38 *
39 * Define a linear function z = a + (b - a) * x + (c - a) * y, where a, b, and c are its
40 * values at points A, B, and C, respectively.
41 *
42 * Our goal is to find the fraction of the area of ABC in where z > 0.
43 *
44 * We note that z(x,y) is continuous, so unless a, b, and c have the same sign the line
45 *
46 * z = 0
47 *
48 * will intersect exactly two of the sides (possibly at a node of ABC).
49 *
50 * So, if the line (z = 0) does not intersect BC, for example, then it has to intersect AB
51 * and AC.
52 *
53 * This method can be applied to arbitrary triangles. It does not even matter if values at
54 * triangle nodes (a, b, c) are listed in the same order. (For any two triangles on a
55 * plane there exists an affine map that takes one to the other. Also, affine maps
56 * preserve ratios of areas of figures.)
57 */
58
59/*!
60 * Compute the area of a triangle on a plane using the "shoelace formula."
61 */
62static inline double triangle_area(const Point &a, const Point &b, const Point &c) {
63 // note: fabs should not be needed since we traverse all triangle nodes
64 // counter-clockwise, but it is good to be safe
65 return 0.5 * std::fabs((a.x - c.x) * (b.y - a.y) - (a.x - b.x) * (c.y - a.y));
66}
67
68/*!
69 * Compute the coordinates of the intersection of (z = 0) with the side AB.
70 */
71Point intersect_ab(double a, double b) {
72 if (a != b) {
73 return {a / (a - b), 0.0};
74 }
75 return {-1.0, -1.0}; // no intersection
76}
77
78/*!
79 * Compute the coordinates of the intersection of (z = 0) with the side BC.
80 */
81Point intersect_bc(double b, double c) {
82 if (b != c) {
83 return {c / (c - b), b / (b - c)};
84 }
85 return {-1.0, -1.0}; // no intersection
86}
87
88/*!
89 * Compute the coordinates of the intersection of (z = 0) with the side AC.
90 */
91Point intersect_ac(double a, double c) {
92 if (a != c) {
93 return {0.0, a / (a - c)};
94 }
95 return {-1.0, -1.0}; // no intersection
96}
97
98/*!
99 * Return true if a point p is not a valid point on a side of the triangle
100 * (0,0)-(1,0)-(0,1).
101 *
102 * This is not a complete test (for example, it does not check if y = 1 - x for points on
103 * the hypotenuse). The point of this is to exclude the kinds of invalid points we are
104 * likely to see, not all of them.
105 *
106 * Note that we use (-1, -1) to indicate "invalid" points in the rest of the code and
107 * these are easy to detect: they require only one comparison.
108 */
109bool invalid(const Point &p) {
110 return (p.x < 0.0 or p.x > 1.0 or p.y < 0.0 or p.y > 1.0);
111}
112
113/*!
114 * Return true if two points are the same.
115 */
116static bool same(const Point &a, const Point &b) {
117 const double threshold = 1e-12;
118 return std::fabs(a.x - b.x) < threshold and std::fabs(a.y - b.y) < threshold;
119}
120
121/*!
122 * Consider the right triangle A(0,0) - B(1,0) - C(0,1).
123 *
124 * Define a linear function z = a + (b - a) * x + (c - a) * y, where a, b, and c are its
125 * values at points A, B, and C, respectively.
126 *
127 * Our goal is to find the fraction of the triangle ABC in where z > 0.
128 *
129 * This corresponds to the grounded area fraction if z is the flotation criterion
130 * function.
131 */
132double grounded_area_fraction(double a, double b, double c) {
133
134 assert(std::isfinite(a));
135 assert(std::isfinite(b));
136 assert(std::isfinite(c));
137
138 if (a > 0.0 and b > 0.0 and c > 0.0) {
139 return 1.0;
140 }
141
142 if (a <= 0.0 and b <= 0.0 and c <= 0.0) {
143 return 0.0;
144 }
145
146 // the area of the triangle (0,0)-(1,0)-(0,1)
147 const double total_area = 0.5;
148
149 const Point
150 ab = intersect_ab(a, b),
151 bc = intersect_bc(b, c),
152 ac = intersect_ac(a, c);
153
154 if (invalid(bc)) {
155 assert(not (invalid(ab) or invalid(ac)));
156
157 double ratio = triangle_area({0.0, 0.0}, ab, ac) / total_area;
158 assert((ratio >= 0.0) and (ratio <= 1.0));
159
160 if (a > 0.0) {
161 return ratio;
162 }
163 return 1.0 - ratio;
164 }
165
166 if (invalid(ac)) {
167 assert(not (invalid(ab) or invalid(bc)));
168
169 double ratio = triangle_area({1.0, 0.0}, bc, ab) / total_area;
170 assert((ratio >= 0.0) and (ratio <= 1.0));
171
172 if (b > 0.0) {
173 return ratio;
174 }
175 return 1.0 - ratio;
176 }
177
178 if (invalid(ab)) {
179 assert(not (invalid(bc) or invalid(ac)));
180
181 double ratio = triangle_area({0.0, 1.0}, ac, bc) / total_area;
182 assert((ratio >= 0.0) and (ratio <= 1.0));
183
184 if (c > 0.0) {
185 return ratio;
186 }
187 return 1.0 - ratio;
188 }
189
190 // Note that we know that ab, bc, and ac are all valid.
191
192 // the a == 0 case, the line F = 0 goes through A
193 if (same(ab, ac)) {
194 double ratio = triangle_area({1.0, 0.0}, bc, ab) / total_area;
195 assert((ratio >= 0.0) and (ratio <= 1.0));
196
197 if (b > 0.0) {
198 return ratio;
199 }
200 return 1.0 - ratio;
201 }
202
203 // the b == 0 case and the c == 0 case
204 if (same(ab, bc) or same(ac, bc)) {
205 double ratio = triangle_area({0.0, 0.0}, ab, ac) / total_area;
206 assert((ratio >= 0.0) and (ratio <= 1.0));
207
208 if (a > 0.0) {
209 return ratio;
210 }
211 return 1.0 - ratio;
212 }
213
214 // Note: the case of F=0 coinciding with a side of the triangle is covered by if clauses
215 // above. For example, when F=0 coincides with AC, we have a = c = 0 and intersect_ac(a, c)
216 // returns an invalid intersection point.
217
219 "the logic in grounded_area_fraction failed! Please submit a bug report.");
220}
221
222/*!
223 * The flotation criterion.
224 */
225static double F(double SL, double B, double H, double alpha) {
226 double
227 water_depth = SL - B,
228 shelf_depth = H * alpha;
229 return shelf_depth - water_depth;
230}
231
232
233/*!
234 * Compute the flotation criterion at all the points in the box stencil.
235 */
237static Box F(const Box &SL, const Box &B, const Box &H, double alpha) {
238 return {F(SL.c, B.c, H.c, alpha),
239 F(SL.n, B.n, H.n, alpha),
240 F(SL.nw, B.nw, H.nw, alpha),
241 F(SL.w, B.w, H.w, alpha),
242 F(SL.sw, B.sw, H.sw, alpha),
243 F(SL.s, B.s, H.s, alpha),
244 F(SL.se, B.se, H.se, alpha),
245 F(SL.e, B.e, H.e, alpha),
246 F(SL.ne, B.ne, H.ne, alpha)};
247}
248
249/*!
250 * @param[in] ice_density ice density, kg/m3
251 * @param[in] ocean_density ocean_density, kg/m3
252 * @param[in] sea_level sea level (flotation) elevation, m
253 * @param[in] ice_thickness ice thickness, m
254 * @param[in] bed_topography bed elevation, m
255 * @param[out] result grounded cell fraction, between 0 (floating) and 1 (grounded)
256 */
257void compute_grounded_cell_fraction(double ice_density,
258 double ocean_density,
259 const array::Scalar1 &sea_level,
260 const array::Scalar1 &ice_thickness,
261 const array::Scalar1 &bed_topography,
262 array::Scalar &result) {
263 auto grid = result.grid();
264 double alpha = ice_density / ocean_density;
265
266 array::AccessScope list{&sea_level, &ice_thickness, &bed_topography, &result};
267
268 ParallelSection loop(grid->com);
269 try {
270 for (auto p = grid->points(); p; p.next()) {
271 const int i = p.i(), j = p.j();
272
273 /*
274 NW----------------N----------------NE
275 | | |
276 | | |
277 | nw--------n--------ne |
278 | | | | |
279 | | | | |
280 W--------w--------o--------e--------E
281 | | | | |
282 | | | | |
283 | sw--------s--------se |
284 | | |
285 | | |
286 SW----------------S----------------SE
287 */
288
289 // compute the floatation function at 8 points surrounding the current grid point
291 {
292 auto S = sea_level.box(i, j);
293 auto H = ice_thickness.box(i, j);
294 auto B = bed_topography.box(i, j);
295
296 auto x = F(S, B, H, alpha);
297
298 f.c = x.c;
299 f.sw = 0.25 * (x.sw + x.s + x.c + x.w);
300 f.se = 0.25 * (x.s + x.se + x.e + x.c);
301 f.ne = 0.25 * (x.c + x.e + x.ne + x.n);
302 f.nw = 0.25 * (x.w + x.c + x.n + x.nw);
303
304 f.s = 0.5 * (x.c + x.s);
305 f.e = 0.5 * (x.c + x.e);
306 f.n = 0.5 * (x.c + x.n);
307 f.w = 0.5 * (x.c + x.w);
308 }
309
310 // compute the grounding fraction for the current cell by breaking it into 8
311 // triangles
312 double fraction = 0.125 * (grounded_area_fraction(f.c, f.ne, f.n) +
313 grounded_area_fraction(f.c, f.n, f.nw) +
314 grounded_area_fraction(f.c, f.nw, f.w) +
315 grounded_area_fraction(f.c, f.w, f.sw) +
316 grounded_area_fraction(f.c, f.sw, f.s) +
317 grounded_area_fraction(f.c, f.s, f.se) +
318 grounded_area_fraction(f.c, f.se, f.e) +
319 grounded_area_fraction(f.c, f.e, f.ne));
320
321 result(i, j) = clip(fraction, 0.0, 1.0);
322
323 }
324 } catch (...) {
325 loop.failed();
326 }
327 loop.check();
328}
329
330} // end of namespace pism
void failed()
Indicates a failure of a parallel section.
static RuntimeError formatted(const ErrorLocation &location, const char format[],...) __attribute__((format(printf
build a RuntimeError with a formatted message
Makes sure that we call begin_access() and end_access() for all accessed array::Arrays.
Definition Array.hh:64
stencils::Box< T > box(int i, int j) const
Definition Array2D.hh:93
std::shared_ptr< const Grid > grid() const
Definition Array.cc:131
#define PISM_ERROR_LOCATION
static double F(double SL, double B, double H, double alpha)
Point intersect_bc(double b, double c)
double grounded_area_fraction(double a, double b, double c)
static bool same(const Point &a, const Point &b)
Point intersect_ac(double a, double c)
T clip(T x, T a, T b)
void compute_grounded_cell_fraction(double ice_density, double ocean_density, const array::Scalar1 &sea_level, const array::Scalar1 &ice_thickness, const array::Scalar1 &bed_topography, array::Scalar &result)
stencils::Box< double > Box
bool invalid(const Point &p)
static double triangle_area(const Point &a, const Point &b, const Point &c)
Point intersect_ab(double a, double b)
static double S(unsigned n)
Definition test_cube.c:58