PISM, A Parallel Ice Sheet Model 2.3.0-79cae578d committed by Constantine Khrulev on 2026-03-22
Loading...
Searching...
No Matches
PicoGeometry.cc
Go to the documentation of this file.
1/* Copyright (C) 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025, 2026 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 <algorithm> // max_element
21#include <queue>
22#include "pism/coupler/ocean/PicoGeometry.hh"
23#include "pism/util/connected_components/label_components.hh"
24#include "pism/util/array/CellType.hh"
25#include "pism/util/pism_utilities.hh"
26
27#include "pism/coupler/util/options.hh"
28#include "pism/util/Interpolation1D.hh"
29#include "pism/util/Profiling.hh"
30#include "pism/util/Logger.hh"
31#include "pism/util/io/IO_Flags.hh"
32
33namespace pism {
34namespace ocean {
35
36PicoGeometry::PicoGeometry(std::shared_ptr<const Grid> grid)
37 : Component(grid),
38 m_continental_shelf(grid, "pico_contshelf_mask"),
39 m_boxes(grid, "pico_box_mask"),
40 m_ice_shelves(grid, "pico_shelf_mask"),
41 m_basin_mask(m_grid, "basins"),
42 m_distance_gl(grid, "pico_distance_gl"),
43 m_distance_cf(grid, "pico_distance_cf"),
44 m_ocean_mask(grid, "pico_ocean_mask"),
45 m_lake_mask(grid, "pico_lake_mask"),
46 m_ice_rises(grid, "pico_ice_rise_mask"),
47 m_tmp(grid, "temporary_storage") {
48
59
60 m_boxes.metadata()["_FillValue"] = {0.0};
61
62 m_ice_rises.metadata()["flag_values"] = {OCEAN, RISE, CONTINENTAL, FLOATING};
63 m_ice_rises.metadata()["flag_meanings"] =
64 "ocean ice_rise continental_ice_sheet, floating_ice";
65
66 m_basin_mask.metadata(0).long_name("mask determines basins for PICO");
67 m_n_basins = 0;
68}
69
73
75 return m_boxes;
76}
77
81
85
87 return m_basin_mask;
88}
89
91
92 ForcingOptions opt(*m_grid->ctx(), "ocean.pico");
93
95
96 m_n_basins = static_cast<int>(max(m_basin_mask)) + 1;
97}
98
99/*!
100 * Compute masks needed by the PICO physics code.
101 *
102 * After this call box_mask(), ice_shelf_mask(), and continental_shelf_mask() will be up
103 * to date.
104 */
105void PicoGeometry::update(const array::Scalar &bed_elevation,
106 const array::CellType1 &cell_type) {
107
108 // Update basin adjacency.
109 //
110 // basin_neighbors() below uses the cell type mask to find
111 // adjacent basins by iterating over the current ice front. This means that basin
112 // adjacency cannot be pre-computed during initialization.
113 {
115
116 // report
117 for (int basin_id = 1; basin_id < m_n_basins; ++basin_id) {
118 const auto &set = m_basin_neighbors[basin_id];
119 if (not set.empty()) {
120 std::vector<std::string> neighbors;
121 for (const auto &n : set) {
122 neighbors.emplace_back(pism::printf("%d", n));
123 }
124 std::string neighbor_list = pism::join(neighbors, ", ");
125 m_log->message(3, "PICO: basin %d neighbors: %s\n", basin_id, neighbor_list.c_str());
126 }
127 }
128 }
129
130 bool exclude_ice_rises = m_config->get_flag("ocean.pico.exclude_ice_rises");
131
132 // these three could be done at the same time
133 {
134 compute_ice_rises(cell_type, exclude_ice_rises, m_ice_rises);
135
137
138 compute_lakes(cell_type, m_lake_mask);
139
140 }
141
142 // these two could be optimized by trying to reduce the number of times we update ghosts
143 {
146
147 profiling().begin("ocean.distances_gl");
149 profiling().end("ocean.distances_gl");
150
151 profiling().begin("ocean.distances_cf");
153 profiling().end("ocean.distances_cf");
154 }
155
156 // computing ice_shelf_mask and box_mask could be done at the same time
157 {
159 auto n_shelves = static_cast<int>(max(m_ice_shelves)) + 1;
160
161 std::vector<int> cfs_in_basins_per_shelf(n_shelves*m_n_basins, 0);
162 std::vector<int> most_shelf_cells_in_basin(n_shelves, 0);
164 most_shelf_cells_in_basin, cfs_in_basins_per_shelf);
165
167 most_shelf_cells_in_basin, cfs_in_basins_per_shelf, n_shelves,
169
170 double continental_shelf_depth = m_config->get_number("ocean.pico.continental_shelf_depth");
171
172 compute_continental_shelf_mask(bed_elevation, m_ice_rises, continental_shelf_depth,
174 }
175
176 int n_boxes = static_cast<int>(m_config->get_number("ocean.pico.number_of_boxes"));
177
179}
180
181
183
184/*!
185 * Re-label components in a mask processed by label_connected_components.
186 *
187 * If type is `BY_AREA`, the biggest one gets the value of 2, all the other ones 1, the
188 * background is set to zero.
189 *
190 * If type is `AREA_THRESHOLD`, patches with areas above `threshold` get the value of 2,
191 * all the other ones 1, the background is set to zero.
192 */
193static void relabel(RelabelingType type,
194 double threshold,
195 array::Scalar &mask) {
196
197 auto grid = mask.grid();
198
199 int max_index = static_cast<int>(array::max(mask));
200
201 if (max_index < 1) {
202 // No components labeled. Fill the mask with zeros and quit.
203 mask.set(0.0);
204 return;
205 }
206
207 std::vector<double> area(max_index + 1, 0.0);
208 std::vector<double> area1(max_index + 1, 0.0);
209 {
210
211 ParallelSection loop(grid->com);
212 try {
213 for (auto p : grid->points()) {
214 const int i = p.i(), j = p.j();
215
216 int index = mask.as_int(i, j);
217
218 if (index > max_index or index < 0) {
219 throw RuntimeError::formatted(PISM_ERROR_LOCATION, "invalid component index: %d", index);
220 }
221
222 if (index > 0) {
223 // count areas of actual components, ignoring the background (index == 0)
224 area[index] += 1.0;
225 }
226 }
227 } catch (...) {
228 loop.failed();
229 }
230 loop.check();
231 GlobalSum(grid->com, area.data(), area1.data(), static_cast<int>(area.size()));
232
233 // copy data
234 area = area1;
235
236 for (unsigned int k = 0; k < area.size(); ++k) {
237 area[k] = grid->cell_area() * area[k];
238 }
239 }
240
241 if (type == BY_AREA) {
242 // find the biggest component
243 int biggest_component = 0;
244 for (unsigned int k = 0; k < area.size(); ++k) {
245 if (area[k] > area[biggest_component]) {
246 biggest_component = static_cast<int>(k);
247 }
248 }
249
250 // re-label
251 for (auto p : grid->points()) {
252 const int i = p.i(), j = p.j();
253
254 int component_index = mask.as_int(i, j);
255
256 if (component_index == biggest_component) {
257 mask(i, j) = 2.0;
258 } else if (component_index > 0) {
259 mask(i, j) = 1.0;
260 } else {
261 mask(i, j) = 0.0;
262 }
263 }
264 } else {
265 for (auto p : grid->points()) {
266 const int i = p.i(), j = p.j();
267
268 int component_index = mask.as_int(i, j);
269
270 if (area[component_index] > threshold) {
271 mask(i, j) = 2.0;
272 } else if (component_index > 0) {
273 mask(i, j) = 1.0;
274 } else {
275 mask(i, j) = 0.0;
276 }
277 }
278 }
279}
280
281/*!
282 * Compute the mask identifying "subglacial lakes", i.e. floating ice areas that are not
283 * connected to the open ocean.
284 *
285 * Resulting mask contains:
286 *
287 * 0 - grounded ice
288 * 1 - floating ice not connected to the open ocean
289 * 2 - floating ice or ice-free ocean connected to the open ocean
290 */
292 profiling().begin("ocean.lakes");
293 {
294 array::AccessScope list{ &cell_type, &m_tmp };
295
296 int background = 0;
297 int floating = 1;
298 int reachable_from_domain_edge = 2;
299 auto grid = cell_type.grid();
300
301 // assume that ocean points (i.e. floating, either icy or ice-free) at the edge of the
302 // domain belong to the "open ocean"
303 for (auto p : grid->points()) {
304 const int i = p.i(), j = p.j();
305
306 if (cell_type.ocean(i, j)) {
307 m_tmp(i, j) = floating;
308
309 if (grid::domain_edge(*grid, i, j)) {
310 m_tmp(i, j) = reachable_from_domain_edge;
311 }
312 } else {
313 m_tmp(i, j) = background;
314 }
315 }
316
317 // identify "floating" areas that are not connected to the open ocean as defined above
318 profiling().begin("ocean.lakes.label");
319 connected_components::label_isolated(m_tmp, reachable_from_domain_edge);
320 profiling().end("ocean.lakes.label");
321
322 result.copy_from(m_tmp);
323 }
324 profiling().end("ocean.lakes");
325}
326
327/*!
328 * Compute the mask identifying ice rises, i.e. grounded ice areas not connected to the
329 * continental ice sheet.
330 *
331 * Resulting mask contains:
332 *
333 * 0 - ocean
334 * 1 - ice rises
335 * 2 - continental ice sheet
336 * 3 - floating ice
337 */
338void PicoGeometry::compute_ice_rises(const array::CellType &cell_type, bool exclude_ice_rises,
339 array::Scalar &result) {
340 profiling().begin("ocean.ice_rises");
341 {
342 array::AccessScope list{ &cell_type, &m_tmp };
343
344 // mask of zeros and ones: one if grounded ice, zero otherwise
345 for (auto p : m_grid->points()) {
346 const int i = p.i(), j = p.j();
347
348 if (cell_type.grounded(i, j)) {
349 m_tmp(i, j) = 2.0;
350 } else {
351 m_tmp(i, j) = 0.0;
352 }
353 }
354
355 profiling().begin("ocean.ice_rises.label");
356 if (exclude_ice_rises) {
358
359 relabel(AREA_THRESHOLD, m_config->get_number("ocean.pico.maximum_ice_rise_area", "m2"),
360 m_tmp);
361 }
362 profiling().end("ocean.ice_rises.label");
363
364 // mark floating ice areas in this mask (reduces the number of masks we need later)
365 for (auto p : m_grid->points()) {
366 const int i = p.i(), j = p.j();
367
368 if (m_tmp(i, j) == 0.0 and cell_type.icy(i, j)) {
369 m_tmp(i, j) = FLOATING;
370 }
371 }
372
373 result.copy_from(m_tmp);
374 }
375 profiling().end("ocean.ice_rises");
376}
377
378/*!
379 * Compute the continental ice shelf mask.
380 *
381 * Resulting mask contains:
382 *
383 * 0 - ocean or icy
384 * 1 - ice-free areas with bed elevation > threshold and not connected to the continental ice sheet
385 * 2 - ice-free areas with bed elevation > threshold, connected to the continental ice sheet
386 */
388 const array::Scalar &ice_rise_mask,
389 double bed_elevation_threshold,
390 array::Scalar &result) {
391 profiling().begin("ocean.continental_shelf_mask");
392 {
393 array::AccessScope list{ &bed_elevation, &ice_rise_mask, &m_tmp };
394
395 for (auto p : m_grid->points()) {
396 const int i = p.i(), j = p.j();
397
398 m_tmp(i, j) = 0.0;
399
400 if (bed_elevation(i, j) > bed_elevation_threshold) {
401 m_tmp(i, j) = 1.0;
402 }
403
404 if (ice_rise_mask.as_int(i, j) == CONTINENTAL) {
405 m_tmp(i, j) = 2.0;
406 }
407 }
408
409 // use "iceberg identification" to label parts *not* connected to the continental ice
410 // sheet
411 profiling().begin("ocean.continental_shelf_mask.label");
413 profiling().end("ocean.continental_shelf_mask.label");
414
415 // At this point areas with bed > threshold are 1, everything else is zero.
416 //
417 // Now we need to mark the continental shelf itself.
418 for (auto p : m_grid->points()) {
419 const int i = p.i(), j = p.j();
420
421 if (m_tmp(i, j) > 0.0) {
422 continue;
423 }
424
425 if (bed_elevation(i, j) > bed_elevation_threshold and ice_rise_mask.as_int(i, j) == OCEAN) {
426 m_tmp(i, j) = 2.0;
427 }
428 }
429
430 result.copy_from(m_tmp);
431 }
432 profiling().end("ocean.continental_shelf_mask");
433}
434
435/*!
436 * Compute the mask identifying ice shelves.
437 *
438 * Each shelf gets an individual integer label.
439 *
440 * Two shelves connected by an ice rise are considered to be parts of the same shelf.
441 *
442 * Floating ice cells that are not connected to the ocean ("subglacial lakes") are
443 * excluded.
444 */
446 const array::Scalar &lake_mask, array::Scalar &result) {
447 profiling().begin("ocean.ice_shelf_mask");
448 {
449 array::AccessScope list{ &ice_rise_mask, &lake_mask, &m_tmp };
450
451 for (auto p : m_grid->points()) {
452 const int i = p.i(), j = p.j();
453
454 int M = ice_rise_mask.as_int(i, j);
455
456 if (M == RISE or M == FLOATING) {
457 m_tmp(i, j) = 1.0;
458 } else {
459 m_tmp(i, j) = 0.0;
460 }
461 }
462
463 profiling().begin("ocean.ice_shelf_mask.label");
465 profiling().end("ocean.ice_shelf_mask.label");
466
467 // remove ice rises and lakes
468 for (auto p : m_grid->points()) {
469 const int i = p.i(), j = p.j();
470
471 if (ice_rise_mask.as_int(i, j) == RISE or lake_mask.as_int(i, j) == 1) {
472 m_tmp(i, j) = 0.0;
473 }
474 }
475
476 result.copy_from(m_tmp);
477 }
478 profiling().end("ocean.ice_shelf_mask");
479}
480
481/*!
482 * Compute the mask identifying ice-free ocean and "holes" in ice shelves.
483 *
484 * Resulting mask contains:
485 *
486 * - 0 - icy cells
487 * - 1 - ice-free ocean which is not connected to the open ocean
488 * - 2 - open ocean
489 *
490 */
492 profiling().begin("ocean.ocean_mask");
493 {
494 array::AccessScope list{ &cell_type, &m_tmp };
495
496 // mask of zeros and ones: one if ice-free ocean, zero otherwise
497 for (auto p : m_grid->points()) {
498 const int i = p.i(), j = p.j();
499
500 if (cell_type.ice_free_ocean(i, j)) {
501 m_tmp(i, j) = 1.0;
502 } else {
503 m_tmp(i, j) = 0.0;
504 }
505 }
506
507 profiling().begin("ocean.ocean_mask.label");
509 profiling().end("ocean.ocean_mask.label");
510
511 relabel(BY_AREA, 0.0, m_tmp);
512
513 result.copy_from(m_tmp);
514 }
515 profiling().end("ocean.ocean_mask");
516}
517
518/*!
519 * Find neighboring basins by checking for the basin boundaries on the ice free ocean.
520 *
521 * Should we identify the intersection at the coastline instead?
522 *
523 * Returns the map from the basin index to a set of indexes of neighbors.
524 */
525std::vector<std::set<int> > PicoGeometry::basin_neighbors(const array::CellType1 &cell_type,
526 const array::Scalar1 &basin_mask) {
528
529 // Allocate the adjacency matrix. This uses twice the amount of storage necessary (the
530 // matrix is symmetric), but in known cases (i.e. with around 20 basins) we're wasting
531 // only ~200*sizeof(int) bytes, which is not that bad (and the code is a bit simpler).
532 std::vector<int> adjacency_matrix(m_n_basins * m_n_basins, 0);
533
534 // short-cuts
535 auto mark_as_neighbors = [&](int b1, int b2) {
536 adjacency_matrix[b1 * m_n_basins + b2] = 1;
537 // preserve symmetry:
538 adjacency_matrix[b2 * m_n_basins + b1] = 1;
539 };
540
541 auto adjacent = [&](int b1, int b2) {
542 return adjacency_matrix[b1 * m_n_basins + b2] > 0;
543 };
544
545 array::AccessScope list{ &cell_type, &basin_mask };
546
547 for (auto p : m_grid->points()) {
548 const int i = p.i(), j = p.j();
549
550 auto B = basin_mask.star_int(i, j);
551
552 bool next_to_icefront = (cell_type.ice_free_ocean(i, j) and cell_type.next_to_ice(i,j));
553
554 // skip the "dummy" basin and cells that are not at the ice front
555 if (B.c == 0 or not next_to_icefront) {
556 continue;
557 }
558
559 // Zero out IDs of basins for cell neighbors that are outside the modeling domain.
560 //
561 // This prevents "wrap-around" at grid boundaries.
562 {
563 B.n *= static_cast<int>(j < (int)m_grid->My() - 1);
564 B.e *= static_cast<int>(i < (int)m_grid->Mx() - 1);
565 B.s *= static_cast<int>(j > 0);
566 B.w *= static_cast<int>(i > 0);
567 }
568
569 auto M = cell_type.star_int(i, j);
570
571 if (ice_free_ocean(M.n)) {
572 mark_as_neighbors(B.c, B.n);
573 }
574
575 if (ice_free_ocean(M.s)) {
576 mark_as_neighbors(B.c, B.s);
577 }
578
579 if (ice_free_ocean(M.e)) {
580 mark_as_neighbors(B.c, B.e);
581 }
582
583 if (ice_free_ocean(M.w)) {
584 mark_as_neighbors(B.c, B.w);
585 }
586 }
587
588 // Make a copy to allreduce efficiently with IntelMPI:
589 {
590 std::vector<int> tmp(adjacency_matrix.size(), 0);
591 GlobalMax(m_grid->com, adjacency_matrix.data(), tmp.data(),
592 static_cast<int>(tmp.size()));
593 // Copy results:
594 adjacency_matrix = tmp;
595 }
596
597 // Convert the matrix into a map "basin ID -> set of neighbors' IDs":
598 std::vector<std::set<int> > result(m_n_basins);
599 for (int b1 = 1; b1 < m_n_basins; ++b1) {
600 for (int b2 = b1 + 1; b2 < m_n_basins; ++b2) {
601 if (adjacent(b1, b2)) {
602 result[b1].insert(b2);
603 result[b2].insert(b1);
604 }
605 }
606 }
607
608 return result;
609}
610
611/*!
612 * Find all basins b, in which the ice shelf s has a calving front with potential ocean water intrusion.
613 * Find the basin bmax, in which the ice shelf s has the most cells
614 */
616 const array::Scalar &basin_mask,
617 const array::Scalar &shelf_mask,
618 int n_shelves,
619 std::vector<int> &most_shelf_cells_in_basin,
620 std::vector<int> &cfs_in_basins_per_shelf) {
621
622 std::vector<int> n_shelf_cells_per_basin(n_shelves * m_n_basins,0);
623 // additional vectors to allreduce efficiently with IntelMPI
624 std::vector<int> n_shelf_cells_per_basinr(n_shelves * m_n_basins,0);
625 std::vector<int> cfs_in_basins_per_shelfr(n_shelves * m_n_basins,0);
626 std::vector<int> most_shelf_cells_in_basinr(n_shelves, 0);
627
628 array::AccessScope list{ &cell_type, &basin_mask, &shelf_mask };
629
630 {
631 for (auto p : m_grid->points()) {
632 const int i = p.i(), j = p.j();
633 int s = shelf_mask.as_int(i, j);
634 int b = basin_mask.as_int(i, j);
635 int sb = s * m_n_basins + b;
636 n_shelf_cells_per_basin[sb]++;
637
638 if (cell_type.as_int(i, j) == MASK_FLOATING) {
639 auto M = cell_type.star(i, j);
640 if (M.n == MASK_ICE_FREE_OCEAN or
641 M.e == MASK_ICE_FREE_OCEAN or
642 M.s == MASK_ICE_FREE_OCEAN or
643 M.w == MASK_ICE_FREE_OCEAN) {
644 if (cfs_in_basins_per_shelf[sb] != b) {
645 cfs_in_basins_per_shelf[sb] = b;
646 }
647 }
648 }
649 }
650
651 GlobalSum(m_grid->com, cfs_in_basins_per_shelf.data(),
652 cfs_in_basins_per_shelfr.data(), n_shelves*m_n_basins);
653 GlobalSum(m_grid->com, n_shelf_cells_per_basin.data(),
654 n_shelf_cells_per_basinr.data(), n_shelves*m_n_basins);
655 // copy values
656 cfs_in_basins_per_shelf = cfs_in_basins_per_shelfr;
657 n_shelf_cells_per_basin = n_shelf_cells_per_basinr;
658
659 for (int s = 0; s < n_shelves; s++) {
660 int n_shelf_cells_per_basin_max = 0;
661 for (int b = 0; b < m_n_basins; b++) {
662 int sb = s * m_n_basins + b;
663 if (n_shelf_cells_per_basin[sb] > n_shelf_cells_per_basin_max) {
664 most_shelf_cells_in_basin[s] = b;
665 n_shelf_cells_per_basin_max = n_shelf_cells_per_basin[sb];
666 }
667 }
668 }
669 }
670}
671
672
673/*!
674 * Find all ice shelves s that spread across non-neighboring basins with calving fronts in those basins and add an ice shelf mask number.
675 */
677 const array::Scalar &basin_mask,
678 const std::vector<std::set<int> > &basin_neighbors,
679 const std::vector<int> &most_shelf_cells_in_basin,
680 const std::vector<int> &cfs_in_basins_per_shelf,
681 int n_shelves,
682 array::Scalar &shelf_mask) {
683
684 assert((int)basin_neighbors.size() == m_n_basins);
685
686 m_tmp.copy_from(shelf_mask);
687
688 std::vector<int> n_shelf_cells_to_split(n_shelves * m_n_basins, 0);
689
690 array::AccessScope list{ &cell_type, &basin_mask, &shelf_mask, &m_tmp };
691
692 for (auto p : m_grid->points()) {
693 const int i = p.i(), j = p.j();
694 if (cell_type.as_int(i, j) == MASK_FLOATING) {
695 int basin = basin_mask.as_int(i, j);
696 int shelf = shelf_mask.as_int(i, j);
697 int b0 = most_shelf_cells_in_basin[shelf];
698
699 bool neighbors = basin_neighbors[basin].count(b0) > 0;
700
701 if (basin != b0 and (not neighbors) and
702 cfs_in_basins_per_shelf[shelf * m_n_basins + basin] > 0) {
703 n_shelf_cells_to_split[shelf * m_n_basins + basin]++;
704 }
705 }
706 }
707
708 {
709 std::vector<int> tmp(n_shelves * m_n_basins, 0);
710 GlobalSum(m_grid->com, n_shelf_cells_to_split.data(),
711 tmp.data(), n_shelves * m_n_basins);
712 // copy values
713 n_shelf_cells_to_split = tmp;
714 }
715
716 // no GlobalSum needed here, only local:
717 std::vector<int> add_shelf_instance(n_shelves * m_n_basins, 0);
718 int n_shelf_numbers_to_add = 0;
719 for (int s = 0; s < n_shelves; s++) {
720 int b0 = most_shelf_cells_in_basin[s];
721 for (int b = 0; b < m_n_basins; b++) {
722 if (n_shelf_cells_to_split[s * m_n_basins + b] > 0) {
723 n_shelf_numbers_to_add += 1;
724 add_shelf_instance[s * m_n_basins + b] = n_shelves + n_shelf_numbers_to_add;
725 m_log->message(3, "\nPICO, split ice shelf s=%d with bmax=%d "
726 "and b=%d and n=%d and si=%d\n", s, b0, b,
727 n_shelf_cells_to_split[s * m_n_basins + b],
728 add_shelf_instance[s * m_n_basins + b]);
729 }
730 }
731 }
732
733 for (auto p : m_grid->points()) {
734 const int i = p.i(), j = p.j();
735 if (cell_type.as_int(i, j) == MASK_FLOATING) {
736 int b = basin_mask.as_int(i, j);
737 int s = shelf_mask.as_int(i, j);
738 if (add_shelf_instance[s * m_n_basins + b] > 0) {
739 m_tmp(i, j) = add_shelf_instance[s * m_n_basins + b];
740 }
741 }
742 }
743
744 shelf_mask.copy_from(m_tmp);
745}
746
747/*!
748 * Compute distance to the grounding line.
749 */
751 const array::Scalar1 &ice_rises,
752 bool exclude_ice_rises,
753 array::Scalar1 &result) {
754
755 array::AccessScope list{ &ice_rises, &ocean_mask, &result };
756
757 result.set(-1);
758
759 // Find the grounding line and the ice front and set result to 1 if ice shelf cell is
760 // next to the grounding line, Ice holes within the shelf are treated like ice shelf
761 // cells, if exclude_ice_rises is set then ice rises are also treated as ice shelf cells.
762
763 ParallelSection loop(m_grid->com);
764 try {
765 for (auto p : m_grid->points()) {
766 const int i = p.i(), j = p.j();
767
768 if (ice_rises.as_int(i, j) == FLOATING or
769 ocean_mask.as_int(i, j) == 1 or
770 (exclude_ice_rises and ice_rises.as_int(i, j) == RISE)) {
771 // if this is an ice shelf cell (or an ice rise) or a hole in an ice shelf
772
773 // label the shelf cells adjacent to the grounding line with 1
774 bool neighbor_to_land =
775 (ice_rises(i, j + 1) == CONTINENTAL or
776 ice_rises(i, j - 1) == CONTINENTAL or
777 ice_rises(i + 1, j) == CONTINENTAL or
778 ice_rises(i - 1, j) == CONTINENTAL or
779 ice_rises(i + 1, j + 1) == CONTINENTAL or
780 ice_rises(i + 1, j - 1) == CONTINENTAL or
781 ice_rises(i - 1, j + 1) == CONTINENTAL or
782 ice_rises(i - 1, j - 1) == CONTINENTAL);
783
784 if (neighbor_to_land) {
785 // i.e. there is a grounded neighboring cell (which is not an ice rise!)
786 result(i, j) = 1;
787 } else {
788 result(i, j) = 0;
789 }
790 }
791 }
792 } catch (...) {
793 loop.failed();
794 }
795 loop.check();
796
797 result.update_ghosts();
798
799 profiling().begin("ocean.eikonal_equation");
800 eikonal_equation(result);
801 profiling().end("ocean.eikonal_equation");
802}
803
804/*!
805 * Compute distance to the calving front.
806 */
808 const array::Scalar &ice_rises,
809 bool exclude_ice_rises,
810 array::Scalar1 &result) {
811
812 array::AccessScope list{ &ice_rises, &ocean_mask, &result };
813
814 result.set(-1);
815
816 ParallelSection loop(m_grid->com);
817 try {
818 for (auto p : m_grid->points()) {
819 const int i = p.i(), j = p.j();
820
821 if (ice_rises.as_int(i, j) == FLOATING or
822 ocean_mask.as_int(i, j) == 1 or
823 (exclude_ice_rises and ice_rises.as_int(i, j) == RISE)) {
824 // if this is an ice shelf cell (or an ice rise) or a hole in an ice shelf
825
826 // label the shelf cells adjacent to the ice front with 1
827 auto M = ocean_mask.star(i, j);
828
829 if (M.n == 2 or M.e == 2 or M.s == 2 or M.w == 2) {
830 // i.e. there is a neighboring open ocean cell
831 result(i, j) = 1;
832 } else {
833 result(i, j) = 0;
834 }
835 }
836 }
837 } catch (...) {
838 loop.failed();
839 }
840 loop.check();
841
842 result.update_ghosts();
843
844 profiling().begin("ocean.eikonal_equation");
845 eikonal_equation(result);
846 profiling().end("ocean.eikonal_equation");
847}
848
849namespace details {
850// Stores indexes of a grid point. Could be replaced with std::pair<int, int>, but
851// `cell.i` and `cell.j` are easier to read than `cell.first` and `cell.second`.
852struct Cell {
853 int i, j;
854};
855} // namespace details
856
857/*!
858 * Find an approximate solution of the Eikonal equation on a given domain.
859 *
860 * To specify the problem, the input field (mask) should be filled with
861 *
862 * - negative values outside the domain
863 * - zeros within the domain
864 * - ones at "wave front" locations
865 *
866 * For example, to compute distances from the grounding line within ice shelves, fill
867 * generic ice shelf locations with zeros, set neighbors of the grounding line to 1, and
868 * the rest of the grid with -1 or some other negative number.
869 *
870 * Implementation details:
871 *
872 * The algorithm starts with mask defined above, then loops over the whole process-local
873 * domain one time. This loop is used to construct a queue containing cells which still
874 * need to be labeled, consisting initially only of cells neighboring the wave front.
875 *
876 * A second loop over the queue then updates each cell based on the minimum non-zero label
877 * of its neighbors and for each updated cell adds its non-updated neighbors to the queue.
878 *
879 * After the queue is empty the ghosts are updated and a check is made on the domain
880 * borders to check if a) still unlabeled cells now have labeled neighbors, or b) if the
881 * label of some neighbor has changed to a smaller value.
882 *
883 * If this is the case, the queue is once more filled with these new cells and the process
884 * repeats.
885 */
887
888 auto grid = mask.grid();
889
890 std::queue<details::Cell> unmarked_cells;
891
892 // Loops over the grid points and creates a queue with all the cells which have not
893 // yet been marked with a distance to the grounding line
894 for (auto p : grid->points()) {
895 const int i = p.i(), j = p.j();
896
897 if (mask.as_int(i, j) == 0) {
898
899 auto R = mask.star(i, j);
900
901 if (R.c == 0 and (R.n == 1 or R.s == 1 or R.e == 1 or R.w == 1)) {
902 // i.e. this is an shelf cell with no distance assigned yet and with a neighbor
903 // that has a distance assigned
904 mask(i, j) = 2;
905 if (R.n == 0) {
906 unmarked_cells.push({i, j + 1});
907 }
908 if (R.s == 0) {
909 unmarked_cells.push({i, j - 1});
910 }
911 if (R.e == 0) {
912 unmarked_cells.push({i + 1, j});
913 }
914 if (R.w == 0) {
915 unmarked_cells.push({i - 1, j});
916 }
917 }
918 }
919 } // end of the loop over grid points
920
921 int global_x_size = (int)grid->Mx();
922 int global_y_size = (int)grid->My();
923
924 int x_size = grid->xm();
925 int x_start = grid->xs();
926 int y_size = grid->ym();
927 int y_start = grid->ys();
928
929 int continue_loop = 1;
930 while (continue_loop != 0) {
931 continue_loop = 0;
932
933 // This is the main loop for updating the distances, it comprises two ways of
934 // operating.
935 //
936 // The first is the initial assignment of distances to unmarked cells, in this case
937 // the unmarked cells have a value of zero.
938 //
939 // The second is the update of cell distances after the update_ghosts call, when there
940 // might be cells in the local domain which are closer to grouding lines in a
941 // neighboring domain.
942 //
943 // In both cases the current cell is marked based on the smallest label of the
944 // neighboring cells.
945 while (not unmarked_cells.empty()) {
946
947 auto cell = unmarked_cells.front();
948 unmarked_cells.pop();
949
950 // Checks if the current cell is inside of the local domain
951 if ((cell.j >= y_start and cell.j < y_start + y_size) and
952 (cell.i >= x_start and cell.i < x_start + x_size)) {
953
954 // Checks if there are neighboring cells in each direction, and if yes takes their values.
955 int north_cell = -1;
956 if (cell.j + 1 < global_y_size) {
957 north_cell = mask.as_int(cell.i, cell.j + 1);
958 }
959 int south_cell = -1;
960 if (cell.j > 0) {
961 south_cell = mask.as_int(cell.i, cell.j - 1);
962 }
963 int east_cell = -1;
964 if (cell.i + 1 < global_x_size) {
965 east_cell = mask.as_int(cell.i + 1, cell.j);
966 }
967 int west_cell = -1;
968 if (cell.i > 0) {
969 west_cell = mask.as_int(cell.i - 1, cell.j);
970 }
971
972 // Update the current cell value only if some of the neighbors are already labeled
973 if (north_cell > 0 or south_cell > 0 or east_cell > 0 or west_cell > 0) {
974
975 // Gets the minimum label in the neighboring cells
976 //
977 // Note: the maximum "distance" in units of grid cells on a given grid is along
978 // the diagonal, which is less than sqrt(2) * max(x_size, y_size). Here we
979 // replace sqrt(2) with 2 to get an easier-to-compute upper bound.
980 int min_label = 2 * (int)std::max(global_x_size, global_y_size);
981 if (north_cell > 0) {
982 min_label = std::min(min_label, north_cell);
983 }
984 if (south_cell > 0) {
985 min_label = std::min(min_label, south_cell);
986 }
987 if (east_cell > 0) {
988 min_label = std::min(min_label, east_cell);
989 }
990 if (west_cell > 0) {
991 min_label = std::min(min_label, west_cell);
992 }
993
994 // If the cell is not zero, the minimum label might be its own
995 int center_cell = mask.as_int(cell.i, cell.j);
996 if (center_cell != 0) {
997 min_label = std::min(min_label, center_cell);
998 }
999
1000 // Update the cell if its value is zero or the minimum label around is smaller
1001 // than original
1002 if (center_cell == 0 or center_cell > min_label + 1) {
1003 mask(cell.i, cell.j) = min_label + 1;
1004 continue_loop = 1;
1005
1006 // Add cells to be updated
1007 if (north_cell == 0 or north_cell > min_label + 2) {
1008 unmarked_cells.push({cell.i, cell.j + 1});
1009 }
1010 if (south_cell == 0 or south_cell > min_label + 2) {
1011 unmarked_cells.push({cell.i, cell.j - 1});
1012 }
1013 if (east_cell == 0 or east_cell > min_label + 2) {
1014 unmarked_cells.push({cell.i + 1, cell.j});
1015 }
1016 if (west_cell == 0 or west_cell > min_label + 2) {
1017 unmarked_cells.push({cell.i - 1, cell.j});
1018 }
1019 }
1020 }
1021 } // end of "if inside the local domain"
1022 } // end of "while (not unmarked_cells.empty())"
1023
1024 mask.update_ghosts();
1025
1026 // Condition used to check if the cell `(i, j)` should be added to the queue
1027 // `unmarked_cells`.
1028 auto add_ij = [&mask](int i, int j, int i_n, int j_n) {
1029 int current = mask.as_int(i, j);
1030 if (current >= 0) {
1031 int neighbor = mask.as_int(i_n, j_n);
1032 if ((neighbor > 0) and (current == 0 or current > neighbor + 1)) {
1033 return true;
1034 }
1035 }
1036 return false;
1037 };
1038
1039 // Loop over the first row of the domain, check the south (j-1) neighbor and add cells
1040 // to the queue if they need updating
1041 {
1042 int j = y_start;
1043 if (j != 0) {
1044 // the south neighbor of (i, j) is valid
1045 for (auto i = x_start; i < x_start + x_size; i++) {
1046 if (add_ij(i, j, i, j - 1)) {
1047 unmarked_cells.push({ i, j });
1048 }
1049 }
1050 }
1051 }
1052
1053 // Loop over the last row of the domain, check the north (j+1) neighbor and add cells
1054 // to the queue if they need updating
1055 {
1056 int j = y_start + y_size - 1;
1057 if (j != global_y_size - 1) {
1058 // the north neighbor of (i, j) is valid
1059 for (auto i = x_start; i < x_start + x_size; i++) {
1060 if (add_ij(i, j, i, j + 1)) {
1061 unmarked_cells.push({ i, j });
1062 }
1063 }
1064 }
1065 }
1066
1067 // Loops over the first column of the domain and add cells to the queue if they need
1068 // updating
1069 {
1070 int i = x_start;
1071 if (i != 0) {
1072 // the west neighbor of (i, j) is valid
1073 for (auto j = y_start; j < y_start + y_size; j++) {
1074 if (add_ij(i, j, i - 1, j)) {
1075 unmarked_cells.push({ i, j });
1076 }
1077 }
1078 }
1079 }
1080
1081 // Loop over the last column of the domain and add cells to the queue if they need updating
1082 {
1083 int i = x_start + x_size - 1;
1084 if (i != global_x_size - 1) {
1085 // the east neighbor of (i, j) is valid
1086 for (auto j = y_start; j < y_start + y_size; j++) {
1087 if (add_ij(i, j, i + 1, j)) {
1088 unmarked_cells.push({ i, j });
1089 }
1090 }
1091 }
1092 }
1093
1094 // If some cell was updated in the global domain or there are still cells
1095 // to be updated, continue the loop
1096 if (not unmarked_cells.empty()) {
1097 continue_loop = 1;
1098 }
1099 continue_loop = GlobalMax(grid->com, continue_loop);
1100 } // end of "while (continue_loop != 0)"
1101}
1102
1103/*!
1104 * Compute the mask identifying ice shelf "boxes" using distances to the grounding line
1105 * and the calving front.
1106 */
1108 const array::Scalar &shelf_mask, int max_number_of_boxes,
1109 array::Scalar &result) {
1110
1111 array::AccessScope list{ &D_gl, &D_cf, &shelf_mask, &result };
1112
1113 int n_shelves = static_cast<int>(array::max(shelf_mask)) + 1;
1114
1115 std::vector<double> GL_distance_max(n_shelves, 0.0);
1116 std::vector<double> GL_distance_max1(n_shelves, 0.0);
1117 std::vector<double> CF_distance_max(n_shelves, 0.0);
1118 std::vector<double> CF_distance_max1(n_shelves, 0.0);
1119
1120 for (auto p : m_grid->points()) {
1121 const int i = p.i(), j = p.j();
1122
1123 int shelf_id = shelf_mask.as_int(i, j);
1124 assert(shelf_id >= 0);
1125 assert(shelf_id < n_shelves + 1);
1126
1127 if (shelf_id == 0) {
1128 // not at a shelf; skip to the next grid point
1129 continue;
1130 }
1131
1132 if (D_gl(i, j) > GL_distance_max[shelf_id]) {
1133 GL_distance_max[shelf_id] = D_gl(i, j);
1134 }
1135
1136 if (D_cf(i, j) > CF_distance_max[shelf_id]) {
1137 CF_distance_max[shelf_id] = D_cf(i, j);
1138 }
1139 }
1140
1141 // compute global maximums
1142 GlobalMax(m_grid->com, GL_distance_max.data(), GL_distance_max1.data(), n_shelves);
1143 GlobalMax(m_grid->com, CF_distance_max.data(), CF_distance_max1.data(), n_shelves);
1144 // copy data
1145 GL_distance_max = GL_distance_max1;
1146 CF_distance_max = CF_distance_max1;
1147
1148 double GL_distance_ref = *std::max_element(GL_distance_max.begin(), GL_distance_max.end());
1149
1150 // compute the number of boxes in each shelf
1151
1152 std::vector<int> n_boxes(n_shelves, 0);
1153 const int n_min = 1;
1154 const double zeta = 0.5;
1155
1156 for (int k = 0; k < n_shelves; ++k) {
1157 n_boxes[k] = n_min + round(pow((GL_distance_max[k] / GL_distance_ref), zeta) * (max_number_of_boxes - n_min));
1158
1159 n_boxes[k] = std::min(n_boxes[k], max_number_of_boxes);
1160 }
1161
1162 result.set(0.0);
1163
1164 for (auto p : m_grid->points()) {
1165 const int i = p.i(), j = p.j();
1166
1167 int d_gl = D_gl.as_int(i, j);
1168 int d_cf = D_cf.as_int(i, j);
1169
1170 if (shelf_mask.as_int(i, j) > 0 and d_gl > 0.0 and d_cf > 0.0) {
1171 int shelf_id = shelf_mask.as_int(i, j);
1172 int n = n_boxes[shelf_id];
1173
1174 // relative position on the shelf (ranges from 0 to 1), increasing towards the
1175 // calving front (see equation 10 in the PICO paper)
1176 double r = d_gl / (double)(d_gl + d_cf);
1177
1178 double C = pow(1.0 - r, 2.0);
1179 for (int k = 0; k < n; ++k) {
1180 if ((n - k - 1) / (double)n <= C and C <= (n - k) / (double)n) {
1181 result(i, j) = std::min(d_gl, k + 1);
1182 }
1183 }
1184 }
1185 }
1186}
1187
1188} // end of namespace ocean
1189} // end of namespace pism
std::shared_ptr< const Grid > grid() const
Definition Component.cc:107
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
const Profiling & profiling() const
Definition Component.cc:115
std::shared_ptr< const Logger > m_log
logger (for easy access)
Definition Component.hh:164
A class defining a common interface for most PISM sub-models.
Definition Component.hh:118
void failed()
Indicates a failure of a parallel section.
void begin(const char *name) const
Definition Profiling.cc:75
void end(const char *name) const
Definition Profiling.cc:91
static RuntimeError formatted(const ErrorLocation &location, const char format[],...) __attribute__((format(printf
build a RuntimeError with a formatted message
VariableMetadata & long_name(const std::string &input)
Makes sure that we call begin_access() and end_access() for all accessed array::Arrays.
Definition Array.hh:66
void copy_from(const Array2D< T > &source)
Definition Array2D.hh:101
stencils::Star< T > star(int i, int j) const
Definition Array2D.hh:107
void set_interpolation_type(InterpolationType type)
Definition Array.cc:181
std::shared_ptr< const Grid > grid() const
Definition Array.cc:134
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
void update_ghosts()
Updates ghost points.
Definition Array.cc:645
VariableMetadata & metadata(unsigned int N=0)
Returns a reference to the VariableMetadata object containing metadata for the compoment N.
Definition Array.cc:467
bool next_to_ice(int i, int j) const
Ice-free margin (at least one of four neighbors has ice).
Definition CellType.hh:87
stencils::Star< int > star_int(int i, int j) const
Definition Scalar.hh:72
bool ice_free_ocean(int i, int j) const
Definition CellType.hh:58
bool ocean(int i, int j) const
Definition CellType.hh:34
bool grounded(int i, int j) const
Definition CellType.hh:38
bool icy(int i, int j) const
Definition CellType.hh:42
"Cell type" mask. Adds convenience methods to array::Scalar.
Definition CellType.hh:30
int as_int(int i, int j) const
Definition Scalar.hh:45
stencils::Star< int > star_int(int i, int j) const
Definition Scalar.hh:72
static Default Nil()
Definition IO_Flags.hh:94
const array::Scalar & continental_shelf_mask() const
void split_ice_shelves(const array::CellType &cell_type, const array::Scalar &basin_mask, const std::vector< std::set< int > > &basin_neighbors, const std::vector< int > &most_shelf_cells_in_basin, const std::vector< int > &cfs_in_basins_per_shelf, int n_shelves, array::Scalar &shelf_mask)
array::Scalar m_continental_shelf
void compute_box_mask(const array::Scalar &D_gl, const array::Scalar &D_cf, const array::Scalar &shelf_mask, int max_number_of_boxes, array::Scalar &result)
PicoGeometry(std::shared_ptr< const Grid > grid)
void compute_ocean_mask(const array::CellType &cell_type, array::Scalar &result)
const array::Scalar & ice_shelf_mask() const
void compute_distances_gl(const array::Scalar &ocean_mask, const array::Scalar1 &ice_rises, bool exclude_ice_rises, array::Scalar1 &result)
void compute_distances_cf(const array::Scalar1 &ocean_mask, const array::Scalar &ice_rises, bool exclude_ice_rises, array::Scalar1 &result)
std::vector< std::set< int > > basin_neighbors(const array::CellType1 &cell_type, const array::Scalar1 &basin_mask)
const array::Scalar & ice_rise_mask() const
void compute_ice_shelf_mask(const array::Scalar &ice_rise_mask, const array::Scalar &lake_mask, array::Scalar &result)
void compute_ice_rises(const array::CellType &cell_type, bool exclude_ice_rises, array::Scalar &result)
std::vector< std::set< int > > m_basin_neighbors
void compute_lakes(const array::CellType &cell_type, array::Scalar &result)
void update(const array::Scalar &bed_elevation, const array::CellType1 &cell_type)
void compute_continental_shelf_mask(const array::Scalar &bed_elevation, const array::Scalar &ice_rise_mask, double bed_elevation_threshold, array::Scalar &result)
const array::Scalar & basin_mask() const
const array::Scalar & box_mask() const
void identify_calving_front_connection(const array::CellType1 &cell_type, const array::Scalar &basin_mask, const array::Scalar &shelf_mask, int n_shelves, std::vector< int > &most_shelf_cells_in_basin, std::vector< int > &cfs_in_basins_per_shelf)
#define PISM_ERROR_LOCATION
static const double b0
Definition exactTestL.cc:41
#define n
Definition exactTestM.c:37
double max(const array::Scalar &input)
Finds maximum over all the values in an array::Scalar object. Ignores ghosts.
Definition Scalar.cc:165
void label_isolated(array::Scalar1 &mask, int reachable)
bool domain_edge(const Grid &grid, int i, int j)
Definition Grid.hh:410
bool ice_free_ocean(int M)
Definition Mask.hh:61
bool ocean(int M)
An ocean cell (floating ice or ice-free).
Definition Mask.hh:40
void eikonal_equation(array::Scalar1 &mask)
static void relabel(RelabelingType type, double threshold, array::Scalar &mask)
void GlobalMax(MPI_Comm comm, double *local, double *result, int count)
std::string printf(const char *format,...)
static const double k
Definition exactTestP.cc:42
@ MASK_FLOATING
Definition Mask.hh:34
@ MASK_ICE_FREE_OCEAN
Definition Mask.hh:35
std::string join(const std::vector< std::string > &strings, const std::string &separator)
Concatenate strings, inserting separator between elements.
void GlobalSum(MPI_Comm comm, double *local, double *result, int count)
std::string filename
Definition options.hh:33