Loading [MathJax]/extensions/tex2jax.js
PISM, A Parallel Ice Sheet Model 2.2.1-cd005eec8 committed by Constantine Khrulev on 2025-03-07
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
connected_components_impl.hh
Go to the documentation of this file.
1/* Copyright (C) 2023, 2024 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/util/array/Scalar.hh"
21
22#include <array>
23#include <vector>
24
25/*!
26 * This file contains the implementation of the *serial* connected component labeling
27 * algorithm using run-length encoding and the disjoint-set data structure
28 * (https://en.wikipedia.org/wiki/Disjoint-set_data_structure).
29 *
30 */
31
32namespace pism {
33namespace connected_components {
34namespace details {
35
36inline int resolve_label(const std::vector<int> &labels, int provisional_label) {
37 int final_label = provisional_label;
38
39 while (final_label != labels[final_label]) {
40 final_label = labels[final_label];
41 }
42
43 return final_label;
44}
45
46struct Run {
48};
49
50/*!
51 * Helper class wrapping a C-style 2D array using the row-major storage order. It is used
52 * to implement the interface required by connected_components::details::label().
53 */
54template<typename T>
55class CArray {
56public:
57 CArray(T *array, int nrows, int ncols) : m_array(array), m_nrows(nrows), m_ncols(ncols) {
58 // empty
59 }
60
61 T &operator()(int r, int c) {
62 return m_array[c + m_ncols * r];
63 }
64
65 T &operator()(int r, int c) const {
66 return m_array[c + m_ncols * r];
67 }
68
69 std::array<int, 2> shape() const {
70 return { m_nrows, m_ncols };
71 }
72
73private:
77};
78
79/*!
80 * Helper class wrapping `pism::array::Scalar` to implement the interface required to use
81 * connected_components::details::label().
82 *
83 * Does *not* own the wrapped array: make sure that the wrapped array outlives the wrapper
84 * to avoid a dangling reference.
85 */
86class PISMArray {
87public:
89 : m_array(array) {
90
91 auto grid = array.grid();
92
93 m_nrows = grid->ym();
94 m_ncols = grid->xm();
95
96 m_c0 = grid->xs();
97 m_r0 = grid->ys();
98
100 }
101
104 }
105
106 inline double &operator()(int r, int c) {
107 return m_array(m_c0 + c, m_r0 + r);
108 }
109
110 inline double &operator()(int r, int c) const {
111 return m_array(m_c0 + c, m_r0 + r);
112 }
113
114 std::array<int,2> shape() const {
115 return {m_nrows, m_ncols};
116 }
117
118private:
122 int m_r0;
123 int m_c0;
124};
125
126//! Adds "foregrdound" and "attached" concepts to an "array".
127template <typename ARRAY>
128class Mask {
129public:
130 Mask(ARRAY &data, int reachable_value = -1) : m_data(data), m_reachable(reachable_value) {
131 // empty
132 }
133
134 std::array<int,2> shape() const {
135 return m_data.shape();
136 }
137
138 inline bool is_foreground(int i, int j) const {
139 return m_data(i, j) > 0;
140 }
141
142 inline bool is_attached(int i, int j) const {
143 return static_cast<int>(m_data(i, j)) == m_reachable;
144 }
145
146private:
147 ARRAY &m_data;
149};
150
151/*!
152 * Label connected components using a *serial* algorithm. Designed to be used on its own
153 * or as a part of the "parallel" implementation.
154 *
155 * Uses `input` to generate the mask of patches "on the fly", saves results to `output`.
156 *
157 * This is implemented as a template function to allow using different "2D array" types.
158 *
159 * The "mask" type has to implement methods
160 *
161 * - `bool is_foreground(row, col)` indicating if a grid cell is a "foreground" cell (part
162 * of a "patch" or a "component") or "background".
163 *
164 * - `bool is_attached(row, col)` indicating if a grid cell is "attached". Patches that do
165 * not contain any "attached" cells are considered "isolated". This method is called
166 * only if `identify_isolated_patches` is `true`.
167 *
168 * The "array" type has to implement `number_t& operator(row, col)` for some numeric type
169 * `number_t` (int, double, etc). The argument `output` is "write only".
170 *
171 * If `assign_final_labels` is true, then set labels to "final" values. This is
172 * appropriate when this function is used on its own (serial algorithm).
173 *
174 * Specifically:
175 *
176 * - If `assign_final_labels` is true and `identify_isolated_patches` is true, then
177 * isolated patches are marked with `1` and the rest are set to `0`.
178 *
179 * - If `assign_final_labels` is true and `identify_isolated_patches` is false, then
180 patches are labeled with consecutive numbers starting from `min_label`.
181 *
182 * - If `assign_final_labels` is false then patches "attached" to cells where
183 * `is_attached(i, j)` is true are marked with the smallest odd number that is greater
184 * than or equal to `min_label`. All other patches get consecutive *even* labels. This
185 * labeling scheme is used by the parallel version of this code.
186 *
187 * Uses labels starting from `min_label`. In the parallel implementation each MPI rank
188 * (each sub-domain) has to use *unique* labels; `min_labels` is used to guarantee this.
189 *
190 * Note: only foreground cells of `output` are updated.
191 */
192template <typename mask, typename array>
193bool label(const mask &input, bool identify_isolated_patched, int min_label,
194 bool assign_final_labels, array &output) {
195
196 // iteration limits:
197 const int col_min = 0;
198 const int row_min = 0;
199 auto shape = input.shape();
200 const int nrows = shape[0];
201 const int ncols = shape[1];
202 // Labels: the "attached" label should be the smallest one.
203 const int background = 0;
204 const int attached_label = 1;
205 const int min_provisional_label = 2;
206
207 // This array encodes equivalences between labels: label k is
208 // equivalent to labels[k], which is equivalent to
209 // labels[labels[k]], etc. The smallest label from a set of
210 // equivalent ones is used as a "representative" label for the set.
211 // By design labels[k] <= k.
212 std::vector<int> labels = {background, attached_label};
213 int provisional_label = min_provisional_label;
214
215 std::vector<Run> runs;
216
217 // storage for labels assigned to pixels in the row above the current one
218 std::vector<int> row_above(ncols, background);
219
220 for (int r = row_min; r < nrows; ++r) {
221
222 int c = col_min;
223 while (c < ncols) {
224 if (input.is_foreground(r, c)) {
225 // we're looking at a foreground pixel that must be the
226 // beginning of a run
227 int L = provisional_label;
228 int c_start = c;
229
230 runs.push_back({ r /* row */, c_start /* column */, 0 /* length */, L /* label */});
231 Run &current_run = runs.back();
232
233 // Iterate over all the pixels in this run
234 while (c < ncols and input.is_foreground(r, c)) {
235
236 if (identify_isolated_patched and input.is_attached(r, c)) {
237 // looking at a pixel attached to a "grounded" area
238 if (L != provisional_label) {
239 labels[L] = attached_label;
240 }
241 L = attached_label;
242 }
243
244 int T = row_above[c];
245 c += 1;
246
247 if (T != background) {
248 // foreground pixel in the row above
249 if (T < L) {
250 if (L != provisional_label) {
251 labels[L] = T;
252 }
253 L = T;
254 } else if (T > L) {
255 labels[T] = L;
256 }
257 }
258 } // end of the loop over pixels in a run
259
260 if (L == provisional_label) {
261 // Failed to assign a label by looking at the row above: we
262 // need to add this label to the array encoding
263 // equivalences.
264 labels.push_back(L);
265 provisional_label += 1;
266 }
267
268 // Done with a run: record the length and the label.
269 current_run.length = c - c_start;
270 current_run.label = L;
271
272 // Record pixel labels in the row above.
273 for (int n = 0; n < current_run.length; ++n) {
274 row_above[c_start + n] = L;
275 }
276 } else {
277 // background pixel
278 row_above[c] = background;
279 c += 1;
280 }
281 } // end of the loop over columns
282 } // end of the loop over rows
283
284 auto N_labels = static_cast<int>(labels.size());
285
286 // Flatten the table of equivalences
287 {
288 for (int k = 0; k < N_labels; ++k) {
289 labels[k] = resolve_label(labels, k);
290 }
291 }
292
293 // Rename labels.
294 //
295 if (assign_final_labels) {
296 // this block is executed by the serial version
297 if (identify_isolated_patched) {
298 // isolated patches are labeled with "1", the rest with "0":
299 for (int k = 0; k < N_labels; ++k) {
300 labels[k] = (labels[k] == attached_label) ? 0 : 1;
301 }
302 } else {
303 // Use consecutive numbers starting from `min_label`.
304 int L = min_label;
305 for (int k = min_provisional_label; k < N_labels; ++k) {
306 if (labels[k] == k) {
307 labels[k] = L;
308 L += 1;
309 } else {
310 labels[k] = labels[labels[k]];
311 }
312 }
313 }
314 } else {
315 // This block is executed by the parallel version (we need to use unique labels to
316 // build the graph describing connections between patches on different sub-domains).
317 //
318 //
319 // Use the smallest allowed odd number to indicate "attached" cells:
320 const int attached = min_label + (1 - (min_label % 2));
321
322 // Use consecutive even numbers for the rest:
323 int L = attached + 1;
324 for (int k = min_provisional_label; k < N_labels; ++k) {
325 if (labels[k] == attached_label) {
326 labels[k] = attached;
327 } else if (labels[k] == k) {
328 labels[k] = L;
329 L += 2;
330 } else {
331 labels[k] = labels[labels[k]];
332 }
333 }
334 }
335
336 // Second scan: assign labels. Note that this does not touch background pixels (we
337 // iterate over runs).
338 for (int k = 0; k < (int)runs.size(); ++k) {
339 auto &r = runs[k];
340 int L = labels[r.label];
341 for (int n = 0; n < r.length; ++n) {
342 output(r.row, r.col + n) = L;
343 }
344 }
345
346 return (not runs.empty());
347}
348
349} // end of namespace details
350} // end of namespace connected_components
351} // end of namespace pism
virtual void end_access() const
Checks if an Array is allocated and calls DAVecRestoreArray.
Definition Array.cc:589
virtual void begin_access() const
Checks if an Array is allocated and calls DAVecGetArray.
Definition Array.cc:568
std::shared_ptr< const Grid > grid() const
Definition Array.cc:131
Adds "foregrdound" and "attached" concepts to an "array".
static const double L
Definition exactTestL.cc:40
#define n
Definition exactTestM.c:37
int resolve_label(const std::vector< int > &labels, int provisional_label)
bool label(const mask &input, bool identify_isolated_patched, int min_label, bool assign_final_labels, array &output)
static const double k
Definition exactTestP.cc:42