PISM, A Parallel Ice Sheet Model  stable v2.1-1-g6902d5502 committed by Ed Bueler on 2023-12-20 08:38:27 -0800
PicoGeometry.cc
Go to the documentation of this file.
1 /* Copyright (C) 2018, 2019, 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 <algorithm> // max_element
21 #include "pism/coupler/ocean/PicoGeometry.hh"
22 #include "pism/util/label_components.hh"
23 #include "pism/util/array/CellType.hh"
24 #include "pism/util/pism_utilities.hh"
25 #include "pism/util/petscwrappers/Vec.hh"
26 
27 #include "pism/coupler/util/options.hh"
28 #include "pism/util/interpolation.hh"
29 
30 namespace pism {
31 namespace ocean {
32 
33 PicoGeometry::PicoGeometry(std::shared_ptr<const Grid> grid)
34  : Component(grid),
35  m_continental_shelf(grid, "pico_contshelf_mask"),
36  m_boxes(grid, "pico_box_mask"),
37  m_ice_shelves(grid, "pico_shelf_mask"),
38  m_basin_mask(m_grid, "basins"),
39  m_distance_gl(grid, "pico_distance_gl"),
40  m_distance_cf(grid, "pico_distance_cf"),
41  m_ocean_mask(grid, "pico_ocean_mask"),
42  m_lake_mask(grid, "pico_lake_mask"),
43  m_ice_rises(grid, "pico_ice_rise_mask"),
44  m_tmp(grid, "temporary_storage") {
45 
56 
57  m_boxes.metadata()["_FillValue"] = {0.0};
58 
59  m_ice_rises.metadata()["flag_values"] = {OCEAN, RISE, CONTINENTAL, FLOATING};
60  m_ice_rises.metadata()["flag_meanings"] =
61  "ocean ice_rise continental_ice_sheet, floating_ice";
62 
63  m_basin_mask.metadata(0).long_name("mask determines basins for PICO");
64  m_n_basins = 0;
65 
67 }
68 
70  return m_continental_shelf;
71 }
72 
74  return m_boxes;
75 }
76 
78  return m_ice_shelves;
79 }
80 
82  return m_ice_rises;
83 }
84 
86  return m_basin_mask;
87 }
88 
90 
91  ForcingOptions opt(*m_grid->ctx(), "ocean.pico");
92 
94 
95  m_n_basins = static_cast<int>(max(m_basin_mask)) + 1;
96 }
97 
98 /*!
99  * Compute masks needed by the PICO physics code.
100  *
101  * After this call box_mask(), ice_shelf_mask(), and continental_shelf_mask() will be up
102  * to date.
103  */
104 void PicoGeometry::update(const array::Scalar &bed_elevation,
105  const array::CellType1 &cell_type) {
106 
107  // Update basin adjacency.
108  //
109  // basin_neighbors() below uses the cell type mask to find
110  // adjacent basins by iterating over the current ice front. This means that basin
111  // adjacency cannot be pre-computed during initialization.
112  {
114 
115  // report
116  for (int basin_id = 1; basin_id < m_n_basins; ++basin_id) {
117  const auto &set = m_basin_neighbors[basin_id];
118  if (not set.empty()) {
119  std::vector<std::string> neighbors;
120  for (const auto &n : set) {
121  neighbors.emplace_back(pism::printf("%d", n));
122  }
123  std::string neighbor_list = pism::join(neighbors, ", ");
124  m_log->message(3, "PICO: basin %d neighbors: %s\n", basin_id, neighbor_list.c_str());
125  }
126  }
127  }
128 
129  bool exclude_ice_rises = m_config->get_flag("ocean.pico.exclude_ice_rises");
130 
131  // these three could be done at the same time
132  {
133  compute_ice_rises(cell_type, exclude_ice_rises, m_ice_rises);
134 
135  compute_ocean_mask(cell_type, m_ocean_mask);
136 
137  compute_lakes(cell_type, m_lake_mask);
138 
139  }
140 
141  // these two could be optimized by trying to reduce the number of times we update ghosts
142  {
145 
147 
149  }
150 
151  // computing ice_shelf_mask and box_mask could be done at the same time
152  {
154  auto n_shelves = static_cast<int>(max(m_ice_shelves)) + 1;
155 
156  std::vector<int> cfs_in_basins_per_shelf(n_shelves*m_n_basins, 0);
157  std::vector<int> most_shelf_cells_in_basin(n_shelves, 0);
159  most_shelf_cells_in_basin, cfs_in_basins_per_shelf);
160 
162  most_shelf_cells_in_basin, cfs_in_basins_per_shelf, n_shelves,
163  m_ice_shelves);
164 
165  double continental_shelf_depth = m_config->get_number("ocean.pico.continental_shelf_depth");
166 
167  compute_continental_shelf_mask(bed_elevation, m_ice_rises, continental_shelf_depth,
169  }
170 
171  int n_boxes = static_cast<int>(m_config->get_number("ocean.pico.number_of_boxes"));
172 
174 }
175 
176 
178 
179 /*!
180  * Re-label components in a mask processed by label_connected_components.
181  *
182  * If type is `BY_AREA`, the biggest one gets the value of 2, all the other ones 1, the
183  * background is set to zero.
184  *
185  * If type is `AREA_THRESHOLD`, patches with areas above `threshold` get the value of 2,
186  * all the other ones 1, the background is set to zero.
187  */
188 static void relabel(RelabelingType type,
189  double threshold,
190  array::Scalar &mask) {
191 
192  auto grid = mask.grid();
193 
194  int max_index = static_cast<int>(array::max(mask));
195 
196  if (max_index < 1) {
197  // No components labeled. Fill the mask with zeros and quit.
198  mask.set(0.0);
199  return;
200  }
201 
202  std::vector<double> area(max_index + 1, 0.0);
203  std::vector<double> area1(max_index + 1, 0.0);
204  {
205 
206  ParallelSection loop(grid->com);
207  try {
208  for (auto p = grid->points(); p; p.next()) {
209  const int i = p.i(), j = p.j();
210 
211  int index = mask.as_int(i, j);
212 
213  if (index > max_index or index < 0) {
214  throw RuntimeError::formatted(PISM_ERROR_LOCATION, "invalid component index: %d", index);
215  }
216 
217  if (index > 0) {
218  // count areas of actual components, ignoring the background (index == 0)
219  area[index] += 1.0;
220  }
221  }
222  } catch (...) {
223  loop.failed();
224  }
225  loop.check();
226  GlobalSum(grid->com, area.data(), area1.data(), area.size());
227 
228  // copy data
229  area = area1;
230 
231  for (unsigned int k = 0; k < area.size(); ++k) {
232  area[k] = grid->cell_area() * area[k];
233  }
234  }
235 
236  if (type == BY_AREA) {
237  // find the biggest component
238  int biggest_component = 0;
239  for (unsigned int k = 0; k < area.size(); ++k) {
240  if (area[k] > area[biggest_component]) {
241  biggest_component = static_cast<int>(k);
242  }
243  }
244 
245  // re-label
246  for (auto p = grid->points(); p; p.next()) {
247  const int i = p.i(), j = p.j();
248 
249  int component_index = mask.as_int(i, j);
250 
251  if (component_index == biggest_component) {
252  mask(i, j) = 2.0;
253  } else if (component_index > 0) {
254  mask(i, j) = 1.0;
255  } else {
256  mask(i, j) = 0.0;
257  }
258  }
259  } else {
260  for (auto p = grid->points(); p; p.next()) {
261  const int i = p.i(), j = p.j();
262 
263  int component_index = mask.as_int(i, j);
264 
265  if (area[component_index] > threshold) {
266  mask(i, j) = 2.0;
267  } else if (component_index > 0) {
268  mask(i, j) = 1.0;
269  } else {
270  mask(i, j) = 0.0;
271  }
272  }
273  }
274 }
275 
276 /*!
277  * Compute the mask identifying "subglacial lakes", i.e. floating ice areas that are not
278  * connected to the open ocean.
279  *
280  * Resulting mask contains:
281  *
282  * 0 - grounded ice
283  * 1 - floating ice not connected to the open ocean
284  * 2 - floating ice or ice-free ocean connected to the open ocean
285  */
287  array::AccessScope list{ &cell_type, &m_tmp };
288 
289  auto grid = cell_type.grid();
290 
291  // assume that ocean points (i.e. floating, either icy or ice-free) at the edge of the
292  // domain belong to the "open ocean"
293  for (auto p = grid->points(); p; p.next()) {
294  const int i = p.i(), j = p.j();
295 
296  if (cell_type.ocean(i, j)) {
297  m_tmp(i, j) = 1.0;
298 
299  if (grid::domain_edge(*grid, i, j)) {
300  m_tmp(i, j) = 2.0;
301  }
302  } else {
303  m_tmp(i, j) = 0.0;
304  }
305  }
306 
307  // identify "floating" areas that are not connected to the open ocean as defined above
308  label_components(m_tmp, *m_tmp_p0, true, 2);
309 
310  result.copy_from(m_tmp);
311 }
312 
313 /*!
314  * Compute the mask identifying ice rises, i.e. grounded ice areas not connected to the
315  * continental ice sheet.
316  *
317  * Resulting mask contains:
318  *
319  * 0 - ocean
320  * 1 - ice rises
321  * 2 - continental ice sheet
322  * 3 - floating ice
323  */
324 void PicoGeometry::compute_ice_rises(const array::CellType &cell_type, bool exclude_ice_rises,
325  array::Scalar &result) {
326  array::AccessScope list{ &cell_type, &m_tmp };
327 
328  // mask of zeros and ones: one if grounded ice, zero otherwise
329  for (auto p = m_grid->points(); p; p.next()) {
330  const int i = p.i(), j = p.j();
331 
332  if (cell_type.grounded(i, j)) {
333  m_tmp(i, j) = 2.0;
334  } else {
335  m_tmp(i, j) = 0.0;
336  }
337  }
338 
339  if (exclude_ice_rises) {
340  label_components(m_tmp, *m_tmp_p0, false, 0);
341 
343  m_config->get_number("ocean.pico.maximum_ice_rise_area", "m2"),
344  m_tmp);
345  }
346 
347  // mark floating ice areas in this mask (reduces the number of masks we need later)
348  for (auto p = m_grid->points(); p; p.next()) {
349  const int i = p.i(), j = p.j();
350 
351  if (m_tmp(i, j) == 0.0 and cell_type.icy(i, j)) {
352  m_tmp(i, j) = FLOATING;
353  }
354  }
355 
356  result.copy_from(m_tmp);
357 }
358 
359 /*!
360  * Compute the continental ice shelf mask.
361  *
362  * Resulting mask contains:
363  *
364  * 0 - ocean or icy
365  * 1 - ice-free areas with bed elevation > threshold and not connected to the continental ice sheet
366  * 2 - ice-free areas with bed elevation > threshold, connected to the continental ice sheet
367  */
369  const array::Scalar &ice_rise_mask,
370  double bed_elevation_threshold,
371  array::Scalar &result) {
372  array::AccessScope list{ &bed_elevation, &ice_rise_mask, &m_tmp };
373 
374  for (auto p = m_grid->points(); p; p.next()) {
375  const int i = p.i(), j = p.j();
376 
377  m_tmp(i, j) = 0.0;
378 
379  if (bed_elevation(i, j) > bed_elevation_threshold) {
380  m_tmp(i, j) = 1.0;
381  }
382 
383  if (ice_rise_mask.as_int(i, j) == CONTINENTAL) {
384  m_tmp(i, j) = 2.0;
385  }
386  }
387 
388  // use "iceberg identification" to label parts *not* connected to the continental ice
389  // sheet
390  label_components(m_tmp, *m_tmp_p0, true, 2.0);
391 
392  // At this point areas with bed > threshold are 1, everything else is zero.
393  //
394  // Now we need to mark the continental shelf itself.
395  for (auto p = m_grid->points(); p; p.next()) {
396  const int i = p.i(), j = p.j();
397 
398  if (m_tmp(i, j) > 0.0) {
399  continue;
400  }
401 
402  if (bed_elevation(i, j) > bed_elevation_threshold and
403  ice_rise_mask.as_int(i, j) == OCEAN) {
404  m_tmp(i, j) = 2.0;
405  }
406  }
407 
408  result.copy_from(m_tmp);
409 }
410 
411 /*!
412  * Compute the mask identifying ice shelves.
413  *
414  * Each shelf gets an individual integer label.
415  *
416  * Two shelves connected by an ice rise are considered to be parts of the same shelf.
417  *
418  * Floating ice cells that are not connected to the ocean ("subglacial lakes") are
419  * excluded.
420  */
421 void PicoGeometry::compute_ice_shelf_mask(const array::Scalar &ice_rise_mask, const array::Scalar &lake_mask,
422  array::Scalar &result) {
423  array::AccessScope list{ &ice_rise_mask, &lake_mask, &m_tmp };
424 
425  for (auto p = m_grid->points(); p; p.next()) {
426  const int i = p.i(), j = p.j();
427 
428  int M = ice_rise_mask.as_int(i, j);
429 
430  if (M == RISE or M == FLOATING) {
431  m_tmp(i, j) = 1.0;
432  } else {
433  m_tmp(i, j) = 0.0;
434  }
435  }
436 
437  label_components(m_tmp, *m_tmp_p0, false, 0);
438 
439  // remove ice rises and lakes
440  for (auto p = m_grid->points(); p; p.next()) {
441  const int i = p.i(), j = p.j();
442 
443  if (ice_rise_mask.as_int(i, j) == RISE or lake_mask.as_int(i, j) == 1) {
444  m_tmp(i, j) = 0.0;
445  }
446  }
447 
448  result.copy_from(m_tmp);
449 }
450 
451 /*!
452  * Compute the mask identifying ice-free ocean and "holes" in ice shelves.
453  *
454  * Resulting mask contains:
455  *
456  * - 0 - icy cells
457  * - 1 - ice-free ocean which is not connected to the open ocean
458  * - 2 - open ocean
459  *
460  */
462  array::AccessScope list{ &cell_type, &m_tmp };
463 
464  // mask of zeros and ones: one if ice-free ocean, zero otherwise
465  for (auto p = m_grid->points(); p; p.next()) {
466  const int i = p.i(), j = p.j();
467 
468  if (cell_type.ice_free_ocean(i, j)) {
469  m_tmp(i, j) = 1.0;
470  } else {
471  m_tmp(i, j) = 0.0;
472  }
473  }
474 
475  label_components(m_tmp, *m_tmp_p0, false, 0);
476 
477  relabel(BY_AREA, 0.0, m_tmp);
478 
479  result.copy_from(m_tmp);
480 }
481 
482 /*!
483  * Find neighboring basins by checking for the basin boundaries on the ice free ocean.
484  *
485  * Should we identify the intersection at the coastline instead?
486  *
487  * Returns the map from the basin index to a set of indexes of neighbors.
488  */
489 std::vector<std::set<int> > PicoGeometry::basin_neighbors(const array::CellType1 &cell_type,
490  const array::Scalar1 &basin_mask) {
491  using mask::ice_free_ocean;
492 
493  // Allocate the adjacency matrix. This uses twice the amount of storage necessary (the
494  // matrix is symmetric), but in known cases (i.e. with around 20 basins) we're wasting
495  // only ~200*sizeof(int) bytes, which is not that bad (and the code is a bit simpler).
496  std::vector<int> adjacency_matrix(m_n_basins * m_n_basins, 0);
497 
498  // short-cuts
499  auto mark_as_neighbors = [&](int b1, int b2) {
500  adjacency_matrix[b1 * m_n_basins + b2] = 1;
501  // preserve symmetry:
502  adjacency_matrix[b2 * m_n_basins + b1] = 1;
503  };
504 
505  auto adjacent = [&](int b1, int b2) {
506  return adjacency_matrix[b1 * m_n_basins + b2] > 0;
507  };
508 
509  array::AccessScope list{ &cell_type, &basin_mask };
510 
511  for (auto p = m_grid->points(); p; p.next()) {
512  const int i = p.i(), j = p.j();
513 
514  auto B = basin_mask.star(i, j);
515 
516  bool next_to_icefront = (cell_type.ice_free_ocean(i, j) and cell_type.next_to_ice(i,j));
517 
518  // skip the "dummy" basin and cells that are not at the ice front
519  if (B.c == 0 or not next_to_icefront) {
520  continue;
521  }
522 
523  // Zero out IDs of basins for cell neighbors that are outside the modeling domain.
524  //
525  // This prevents "wrap-around" at grid boundaries.
526  {
527  B.n *= static_cast<int>(j < (int)m_grid->My() - 1);
528  B.e *= static_cast<int>(i < (int)m_grid->Mx() - 1);
529  B.s *= static_cast<int>(j > 0);
530  B.w *= static_cast<int>(i > 0);
531  }
532 
533  auto M = cell_type.star_int(i, j);
534 
535  if (ice_free_ocean(M.n)) {
536  mark_as_neighbors(B.c, B.n);
537  }
538 
539  if (ice_free_ocean(M.s)) {
540  mark_as_neighbors(B.c, B.s);
541  }
542 
543  if (ice_free_ocean(M.e)) {
544  mark_as_neighbors(B.c, B.e);
545  }
546 
547  if (ice_free_ocean(M.w)) {
548  mark_as_neighbors(B.c, B.w);
549  }
550  }
551 
552  // Make a copy to allreduce efficiently with IntelMPI:
553  {
554  std::vector<int> tmp(adjacency_matrix.size(), 0);
555  GlobalMax(m_grid->com, adjacency_matrix.data(), tmp.data(),
556  static_cast<int>(tmp.size()));
557  // Copy results:
558  adjacency_matrix = tmp;
559  }
560 
561  // Convert the matrix into a map "basin ID -> set of neighbors' IDs":
562  std::vector<std::set<int> > result(m_n_basins);
563  for (int b1 = 1; b1 < m_n_basins; ++b1) {
564  for (int b2 = b1 + 1; b2 < m_n_basins; ++b2) {
565  if (adjacent(b1, b2)) {
566  result[b1].insert(b2);
567  result[b2].insert(b1);
568  }
569  }
570  }
571 
572  return result;
573 }
574 
575 /*!
576  * Find all basins b, in which the ice shelf s has a calving front with potential ocean water intrusion.
577  * Find the basin bmax, in which the ice shelf s has the most cells
578  */
580  const array::Scalar &basin_mask,
581  const array::Scalar &shelf_mask,
582  int n_shelves,
583  std::vector<int> &most_shelf_cells_in_basin,
584  std::vector<int> &cfs_in_basins_per_shelf) {
585 
586  std::vector<int> n_shelf_cells_per_basin(n_shelves * m_n_basins,0);
587  // additional vectors to allreduce efficiently with IntelMPI
588  std::vector<int> n_shelf_cells_per_basinr(n_shelves * m_n_basins,0);
589  std::vector<int> cfs_in_basins_per_shelfr(n_shelves * m_n_basins,0);
590  std::vector<int> most_shelf_cells_in_basinr(n_shelves, 0);
591 
592  array::AccessScope list{ &cell_type, &basin_mask, &shelf_mask };
593 
594  {
595  for (auto p = m_grid->points(); p; p.next()) {
596  const int i = p.i(), j = p.j();
597  int s = shelf_mask.as_int(i, j);
598  int b = basin_mask.as_int(i, j);
599  int sb = s * m_n_basins + b;
600  n_shelf_cells_per_basin[sb]++;
601 
602  if (cell_type.as_int(i, j) == MASK_FLOATING) {
603  auto M = cell_type.star(i, j);
604  if (M.n == MASK_ICE_FREE_OCEAN or
605  M.e == MASK_ICE_FREE_OCEAN or
606  M.s == MASK_ICE_FREE_OCEAN or
607  M.w == MASK_ICE_FREE_OCEAN) {
608  if (cfs_in_basins_per_shelf[sb] != b) {
609  cfs_in_basins_per_shelf[sb] = b;
610  }
611  }
612  }
613  }
614 
615  GlobalSum(m_grid->com, cfs_in_basins_per_shelf.data(),
616  cfs_in_basins_per_shelfr.data(), n_shelves*m_n_basins);
617  GlobalSum(m_grid->com, n_shelf_cells_per_basin.data(),
618  n_shelf_cells_per_basinr.data(), n_shelves*m_n_basins);
619  // copy values
620  cfs_in_basins_per_shelf = cfs_in_basins_per_shelfr;
621  n_shelf_cells_per_basin = n_shelf_cells_per_basinr;
622 
623  for (int s = 0; s < n_shelves; s++) {
624  int n_shelf_cells_per_basin_max = 0;
625  for (int b = 0; b < m_n_basins; b++) {
626  int sb = s * m_n_basins + b;
627  if (n_shelf_cells_per_basin[sb] > n_shelf_cells_per_basin_max) {
628  most_shelf_cells_in_basin[s] = b;
629  n_shelf_cells_per_basin_max = n_shelf_cells_per_basin[sb];
630  }
631  }
632  }
633  }
634 }
635 
636 
637 /*!
638  * Find all ice shelves s that spread across non-neighboring basins with calving fronts in those basins and add an ice shelf mask number.
639  */
641  const array::Scalar &basin_mask,
642  const std::vector<std::set<int> > &basin_neighbors,
643  const std::vector<int> &most_shelf_cells_in_basin,
644  const std::vector<int> &cfs_in_basins_per_shelf,
645  int n_shelves,
646  array::Scalar &shelf_mask) {
647 
648  assert((int)basin_neighbors.size() == m_n_basins);
649 
650  m_tmp.copy_from(shelf_mask);
651 
652  std::vector<int> n_shelf_cells_to_split(n_shelves * m_n_basins, 0);
653 
654  array::AccessScope list{ &cell_type, &basin_mask, &shelf_mask, &m_tmp };
655 
656  for (auto p = m_grid->points(); p; p.next()) {
657  const int i = p.i(), j = p.j();
658  if (cell_type.as_int(i, j) == MASK_FLOATING) {
659  int basin = basin_mask.as_int(i, j);
660  int shelf = shelf_mask.as_int(i, j);
661  int b0 = most_shelf_cells_in_basin[shelf];
662 
663  bool neighbors = basin_neighbors[basin].count(b0) > 0;
664 
665  if (basin != b0 and (not neighbors) and
666  cfs_in_basins_per_shelf[shelf * m_n_basins + basin] > 0) {
667  n_shelf_cells_to_split[shelf * m_n_basins + basin]++;
668  }
669  }
670  }
671 
672  {
673  std::vector<int> tmp(n_shelves * m_n_basins, 0);
674  GlobalSum(m_grid->com, n_shelf_cells_to_split.data(),
675  tmp.data(), n_shelves * m_n_basins);
676  // copy values
677  n_shelf_cells_to_split = tmp;
678  }
679 
680  // no GlobalSum needed here, only local:
681  std::vector<int> add_shelf_instance(n_shelves * m_n_basins, 0);
682  int n_shelf_numbers_to_add = 0;
683  for (int s = 0; s < n_shelves; s++) {
684  int b0 = most_shelf_cells_in_basin[s];
685  for (int b = 0; b < m_n_basins; b++) {
686  if (n_shelf_cells_to_split[s * m_n_basins + b] > 0) {
687  n_shelf_numbers_to_add += 1;
688  add_shelf_instance[s * m_n_basins + b] = n_shelves + n_shelf_numbers_to_add;
689  m_log->message(3, "\nPICO, split ice shelf s=%d with bmax=%d "
690  "and b=%d and n=%d and si=%d\n", s, b0, b,
691  n_shelf_cells_to_split[s * m_n_basins + b],
692  add_shelf_instance[s * m_n_basins + b]);
693  }
694  }
695  }
696 
697  for (auto p = m_grid->points(); p; p.next()) {
698  const int i = p.i(), j = p.j();
699  if (cell_type.as_int(i, j) == MASK_FLOATING) {
700  int b = basin_mask.as_int(i, j);
701  int s = shelf_mask.as_int(i, j);
702  if (add_shelf_instance[s * m_n_basins + b] > 0) {
703  m_tmp(i, j) = add_shelf_instance[s * m_n_basins + b];
704  }
705  }
706  }
707 
708  shelf_mask.copy_from(m_tmp);
709 }
710 
711 /*!
712  * Compute distance to the grounding line.
713  */
715  const array::Scalar1 &ice_rises,
716  bool exclude_ice_rises,
717  array::Scalar1 &result) {
718 
719  array::AccessScope list{ &ice_rises, &ocean_mask, &result };
720 
721  result.set(-1);
722 
723  // Find the grounding line and the ice front and set result to 1 if ice shelf cell is
724  // next to the grounding line, Ice holes within the shelf are treated like ice shelf
725  // cells, if exclude_ice_rises is set then ice rises are also treated as ice shelf cells.
726 
727  ParallelSection loop(m_grid->com);
728  try {
729  for (auto p = m_grid->points(); p; p.next()) {
730  const int i = p.i(), j = p.j();
731 
732  if (ice_rises.as_int(i, j) == FLOATING or
733  ocean_mask.as_int(i, j) == 1 or
734  (exclude_ice_rises and ice_rises.as_int(i, j) == RISE)) {
735  // if this is an ice shelf cell (or an ice rise) or a hole in an ice shelf
736 
737  // label the shelf cells adjacent to the grounding line with 1
738  bool neighbor_to_land =
739  (ice_rises(i, j + 1) == CONTINENTAL or
740  ice_rises(i, j - 1) == CONTINENTAL or
741  ice_rises(i + 1, j) == CONTINENTAL or
742  ice_rises(i - 1, j) == CONTINENTAL or
743  ice_rises(i + 1, j + 1) == CONTINENTAL or
744  ice_rises(i + 1, j - 1) == CONTINENTAL or
745  ice_rises(i - 1, j + 1) == CONTINENTAL or
746  ice_rises(i - 1, j - 1) == CONTINENTAL);
747 
748  if (neighbor_to_land) {
749  // i.e. there is a grounded neighboring cell (which is not an ice rise!)
750  result(i, j) = 1;
751  } else {
752  result(i, j) = 0;
753  }
754  }
755  }
756  } catch (...) {
757  loop.failed();
758  }
759  loop.check();
760 
761  result.update_ghosts();
762 
763  eikonal_equation(result);
764 }
765 
766 /*!
767  * Compute distance to the calving front.
768  */
770  const array::Scalar &ice_rises,
771  bool exclude_ice_rises,
772  array::Scalar1 &result) {
773 
774  array::AccessScope list{ &ice_rises, &ocean_mask, &result };
775 
776  result.set(-1);
777 
778  ParallelSection loop(m_grid->com);
779  try {
780  for (auto p = m_grid->points(); p; p.next()) {
781  const int i = p.i(), j = p.j();
782 
783  if (ice_rises.as_int(i, j) == FLOATING or
784  ocean_mask.as_int(i, j) == 1 or
785  (exclude_ice_rises and ice_rises.as_int(i, j) == RISE)) {
786  // if this is an ice shelf cell (or an ice rise) or a hole in an ice shelf
787 
788  // label the shelf cells adjacent to the ice front with 1
789  auto M = ocean_mask.star(i, j);
790 
791  if (M.n == 2 or M.e == 2 or M.s == 2 or M.w == 2) {
792  // i.e. there is a neighboring open ocean cell
793  result(i, j) = 1;
794  } else {
795  result(i, j) = 0;
796  }
797  }
798  }
799  } catch (...) {
800  loop.failed();
801  }
802  loop.check();
803 
804  result.update_ghosts();
805 
806  eikonal_equation(result);
807 }
808 
809 /*!
810  * Find an approximate solution of the Eikonal equation on a given domain.
811  *
812  * To specify the problem, the input field (mask) should be filled with
813  *
814  * - negative values outside the domain
815  * - zeros within the domain
816  * - ones at "wave front" locations
817  *
818  * For example, to compute distances from the grounding line within ice shelves, fill
819  * generic ice shelf locations with zeros, set neighbors of the grounding line to 1, and
820  * the rest of the grid with -1 or some other negative number.
821  *
822  * Note that this implementation updates ghosts *every* iteration. We could speed this
823  * up by checking if a point at a boundary of the processor sub-domain was updated and
824  * update ghosts in those cases only.
825  *
826  * FIXME: replace this with a better algorithm.
827  */
829 
830  assert(mask.stencil_width() > 0);
831 
832  auto grid = mask.grid();
833 
834  double current_label = 1;
835  double continue_loop = 1;
836  while (continue_loop != 0) {
837 
838  continue_loop = 0;
839 
840  for (auto p = grid->points(); p; p.next()) {
841  const int i = p.i(), j = p.j();
842 
843  if (mask.as_int(i, j) == 0) {
844 
845  auto R = mask.star(i, j);
846 
847  if (R.c == 0 and
848  (R.n == current_label or R.s == current_label or
849  R.e == current_label or R.w == current_label)) {
850  // i.e. this is an shelf cell with no distance assigned yet and with a neighbor
851  // that has a distance assigned
852  mask(i, j) = current_label + 1;
853  continue_loop = 1;
854  }
855  }
856  } // loop over grid points
857 
858  current_label++;
859  mask.update_ghosts();
860 
861  continue_loop = GlobalMax(grid->com, continue_loop);
862  }
863 }
864 
865 /*!
866  * Compute the mask identifying ice shelf "boxes" using distances to the grounding line
867  * and the calving front.
868  */
870  const array::Scalar &shelf_mask, int max_number_of_boxes,
871  array::Scalar &result) {
872 
873  array::AccessScope list{ &D_gl, &D_cf, &shelf_mask, &result };
874 
875  int n_shelves = static_cast<int>(array::max(shelf_mask)) + 1;
876 
877  std::vector<double> GL_distance_max(n_shelves, 0.0);
878  std::vector<double> GL_distance_max1(n_shelves, 0.0);
879  std::vector<double> CF_distance_max(n_shelves, 0.0);
880  std::vector<double> CF_distance_max1(n_shelves, 0.0);
881 
882  for (auto p = m_grid->points(); p; p.next()) {
883  const int i = p.i(), j = p.j();
884 
885  int shelf_id = shelf_mask.as_int(i, j);
886  assert(shelf_id >= 0);
887  assert(shelf_id < n_shelves + 1);
888 
889  if (shelf_id == 0) {
890  // not at a shelf; skip to the next grid point
891  continue;
892  }
893 
894  if (D_gl(i, j) > GL_distance_max[shelf_id]) {
895  GL_distance_max[shelf_id] = D_gl(i, j);
896  }
897 
898  if (D_cf(i, j) > CF_distance_max[shelf_id]) {
899  CF_distance_max[shelf_id] = D_cf(i, j);
900  }
901  }
902 
903  // compute global maximums
904  GlobalMax(m_grid->com, GL_distance_max.data(), GL_distance_max1.data(), n_shelves);
905  GlobalMax(m_grid->com, CF_distance_max.data(), CF_distance_max1.data(), n_shelves);
906  // copy data
907  GL_distance_max = GL_distance_max1;
908  CF_distance_max = CF_distance_max1;
909 
910  double GL_distance_ref = *std::max_element(GL_distance_max.begin(), GL_distance_max.end());
911 
912  // compute the number of boxes in each shelf
913 
914  std::vector<int> n_boxes(n_shelves, 0);
915  const int n_min = 1;
916  const double zeta = 0.5;
917 
918  for (int k = 0; k < n_shelves; ++k) {
919  n_boxes[k] = n_min + round(pow((GL_distance_max[k] / GL_distance_ref), zeta) * (max_number_of_boxes - n_min));
920 
921  n_boxes[k] = std::min(n_boxes[k], max_number_of_boxes);
922  }
923 
924  result.set(0.0);
925 
926  for (auto p = m_grid->points(); p; p.next()) {
927  const int i = p.i(), j = p.j();
928 
929  int d_gl = D_gl.as_int(i, j);
930  int d_cf = D_cf.as_int(i, j);
931 
932  if (shelf_mask.as_int(i, j) > 0 and d_gl > 0.0 and d_cf > 0.0) {
933  int shelf_id = shelf_mask.as_int(i, j);
934  int n = n_boxes[shelf_id];
935 
936  // relative position on the shelf (ranges from 0 to 1), increasing towards the
937  // calving front (see equation 10 in the PICO paper)
938  double r = d_gl / (double)(d_gl + d_cf);
939 
940  double C = pow(1.0 - r, 2.0);
941  for (int k = 0; k < n; ++k) {
942  if ((n - k - 1) / (double)n <= C and C <= (n - k) / (double)n) {
943  result(i, j) = std::min(d_gl, k + 1);
944  }
945  }
946  }
947  }
948 }
949 
950 } // end of namespace ocean
951 } // end of namespace pism
std::shared_ptr< const Grid > grid() const
Definition: Component.cc:105
const Config::ConstPtr m_config
configuration database used by this component
Definition: Component.hh:158
const Logger::ConstPtr m_log
logger (for easy access)
Definition: Component.hh:162
const std::shared_ptr< const Grid > m_grid
grid used by this component
Definition: Component.hh:156
A class defining a common interface for most PISM sub-models.
Definition: Component.hh:118
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
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:65
void copy_from(const Array2D< T > &source)
Definition: Array2D.hh:73
stencils::Star< T > star(int i, int j) const
Definition: Array2D.hh:79
void set_interpolation_type(InterpolationType type)
Definition: Array.cc:179
std::shared_ptr< const Grid > grid() const
Definition: Array.cc:132
void set(double c)
Result: v[j] <- c for all j.
Definition: Array.cc:707
void regrid(const std::string &filename, io::Default default_value)
Definition: Array.cc:814
std::shared_ptr< petsc::Vec > allocate_proc0_copy() const
Definition: Array.cc:963
void update_ghosts()
Updates ghost points.
Definition: Array.cc:693
unsigned int stencil_width() const
Get the stencil width of the current Array. Returns 0 if ghosts are not available.
Definition: Array.cc:331
SpatialVariableMetadata & metadata(unsigned int N=0)
Returns a reference to the SpatialVariableMetadata object containing metadata for the compoment N.
Definition: Array.cc:553
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
static Default Nil()
Definition: IO_Flags.hh:97
const array::Scalar & continental_shelf_mask() const
Definition: PicoGeometry.cc:69
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
std::shared_ptr< petsc::Vec > m_tmp_p0
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)
array::Scalar1 m_ice_rises
PicoGeometry(std::shared_ptr< const Grid > grid)
Definition: PicoGeometry.cc:33
void compute_ocean_mask(const array::CellType &cell_type, array::Scalar &result)
const array::Scalar & ice_shelf_mask() const
Definition: PicoGeometry.cc:77
array::Scalar1 m_distance_gl
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
Definition: PicoGeometry.cc:81
void compute_ice_shelf_mask(const array::Scalar &ice_rise_mask, const array::Scalar &lake_mask, array::Scalar &result)
array::Scalar1 m_basin_mask
void compute_ice_rises(const array::CellType &cell_type, bool exclude_ice_rises, array::Scalar &result)
array::Scalar1 m_ocean_mask
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
Definition: PicoGeometry.cc:85
const array::Scalar & box_mask() const
Definition: PicoGeometry.cc:73
array::Scalar1 m_distance_cf
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
double min(const array::Scalar &input)
Finds minimum over all the values in an array::Scalar object. Ignores ghosts.
Definition: Scalar.cc:193
bool domain_edge(const Grid &grid, int i, int j)
Definition: Grid.hh:396
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
void label_components(array::Scalar &mask, petsc::Vec &mask_p0, bool identify_icebergs, double mask_grounded)
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)
const int C[]
Definition: ssafd_code.cc:44
std::string filename
Definition: options.hh:33