Loading...
Searching...
No Matches
spherical_shell.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <cmath>
4#include <iostream>
5#include <ranges>
6#include <stdexcept>
7#include <vector>
8
9#include "../grid_types.hpp"
10#include "../terra/kokkos/kokkos_wrapper.hpp"
12#include "dense/vec.hpp"
13#include "mpi/mpi.hpp"
14#include "util/logging.hpp"
15
16namespace terra::grid::shell {
17
18/// @brief Computes the radial shell radii for a uniform grid.
19///
20/// Note that a shell is a 2D manifold in 3D space.
21/// A layer is a 3D volume in 3D space - it is sandwiched by two shells (one on each side).
22///
23/// @param r_min Radius of the innermost shell.
24/// @param r_max Radius of the outermost shell.
25/// @param num_shells Number of shells.
26/// @return Vector of shell radii (uniformly distributed in [r_min, r_max]).
27template < std::floating_point T >
28std::vector< T > uniform_shell_radii( T r_min, T r_max, int num_shells )
29{
30 if ( num_shells < 2 )
31 {
32 Kokkos::abort( "Number of shells must be at least 2." );
33 }
34
35 if ( r_min >= r_max )
36 {
37 Kokkos::abort( "r_min must be strictly less than r_max." );
38 }
39
40 std::vector< T > radii;
41 radii.reserve( num_shells );
42 const T r_step = ( r_max - r_min ) / ( num_shells - 1 );
43 for ( int i = 0; i < num_shells; ++i )
44 {
45 radii.push_back( r_min + i * r_step );
46 }
47
48 // Set boundary exactly.
49 radii[num_shells - 1] = r_max;
50
51 return radii;
52}
53
54/// @brief Map to be used in @ref mapped_shell_radii.
55///
56/// Can be used to have smaller elements near the shell boundaries.
57///
58/// Returns a function
59/// \f[
60/// f(s) := \begin{cases}
61/// s & k \leq 0 \\
62/// \frac{1}{2} \frac{\tanh( k( 2s-1 ) )}{\tanh(k) + 1} & k > 0
63/// \end{cases}
64/// \f]
65///
66/// @param k \f$k = 0\f$ results in a uniform distribution (linear function), \f$k > 0\f$ refines at the boundary
67/// (roughly: \f$k \approx 1\f$: mild clustering, \f$k \approx 2\f$: strong clustering)
68template < std::floating_point T >
69std::function< T( T ) > make_tanh_boundary_cluster( T k )
70{
71 return [k]( T s ) {
72 if ( k <= 0.0 )
73 {
74 return s;
75 }
76
77 T x = T( 2 ) * s - T( 1 ); // map [0,1] -> [-1,1]
78 return ( std::tanh( k * x ) / std::tanh( k ) + T( 1 ) ) * T( 0.5 );
79 };
80}
81
82/// @brief Computes the radial shell radii for a non-uniformly distributed grid.
83///
84/// Note that a shell is a 2D manifold in 3D space.
85/// A layer is a 3D volume in 3D space – it is sandwiched by two shells (one on each side).
86///
87/// The shell radii are generated from a uniform parameter (\f$N\f$ is the number of shells)
88/// \f[
89/// s_i = \frac{i}{N-1}, \; i = 0,\dots,N-1,
90/// \f]
91/// which is mapped to a redistributed parameter
92/// \f[
93/// t_i = f(s_i)
94/// \f]
95/// using a user-supplied function
96/// \f[
97/// f : [0,1] \rightarrow [0,1].
98/// \f]
99/// The physical radii are then computed as
100/// \f[
101/// r_i = r_{\min} + (r_{\max} - r_{\min}) \, t_i .
102/// \f]
103///
104/// The function \f$f(s) = s\f$ therefore maps the shells uniformly.
105/// In areas where the gradient of \f$f\f$ is small, shells are closer together.
106/// In areas where the gradient of \f$f\f$ is large, shells are further apart.
107///
108/// Try @ref make_tanh_boundary_cluster which returns a parameterized function that can be passed to this function.
109///
110/// @tparam T Floating-point type.
111///
112/// @param r_min Radius of the innermost shell.
113/// @param r_max Radius of the outermost shell.
114/// @param num_shells Number of shells \f$ N \f$.
115/// @param map Mapping function \f$ f : [0,1] \rightarrow [0,1] \f$ controlling shell distribution.
116/// It should be monotone increasing and satisfy
117/// \f$ f(0)=0 \f$ and \f$ f(1)=1 \f$.
118///
119/// @return Vector of shell radii (non-uniformly distributed in \f$[r_{\min}, r_{\max}]\f$).
120template < std::floating_point T >
121std::vector< T > mapped_shell_radii( T r_min, T r_max, int num_shells, const std::function< T( T ) >& map )
122{
123 if ( num_shells < 2 )
124 {
125 Kokkos::abort( "Number of shells must be at least 2." );
126 }
127
128 if ( r_min >= r_max )
129 {
130 Kokkos::abort( "r_min must be strictly less than r_max." );
131 }
132
133 std::vector< T > radii;
134 radii.reserve( num_shells );
135
136 const T inv = T( 1 ) / T( num_shells - 1 );
137
138 for ( int i = 0; i < num_shells; ++i )
139 {
140 T s = T( i ) * inv; // uniform in [0,1]
141 T t = map( s ); // redistributed in [0,1]
142 radii.push_back( r_min + ( r_max - r_min ) * t );
143 }
144
145 // Enforce exact boundaries
146 radii.front() = r_min;
147 radii.back() = r_max;
148
149 return radii;
150}
151
152/// @brief Computes the min absolute distance of two entries in the passed vector of shell radii.
153template < std::floating_point T >
154T min_radial_h( const std::vector< T >& shell_radii )
155{
156 if ( shell_radii.size() < 2 )
157 {
158 throw std::runtime_error( " Need at least two shells to compute h. " );
159 }
160
161 T min_dist = std::numeric_limits< T >::infinity();
162 for ( size_t i = 1; i < shell_radii.size(); ++i )
163 {
164 T d = std::abs( shell_radii[i] - shell_radii[i - 1] );
165 if ( d < min_dist )
166 {
167 min_dist = d;
168 }
169 }
170 return min_dist;
171}
172
173/// @brief Computes the max absolute distance of two entries in the passed vector of shell radii.
174template < std::floating_point T >
175T max_radial_h( const std::vector< T >& shell_radii )
176{
177 if ( shell_radii.size() < 2 )
178 {
179 throw std::runtime_error( " Need at least two shells to compute h. " );
180 }
181
182 T min_dist = 0;
183 for ( size_t i = 1; i < shell_radii.size(); ++i )
184 {
185 T d = std::abs( shell_radii[i] - shell_radii[i - 1] );
186 if ( d > min_dist )
187 {
188 min_dist = d;
189 }
190 }
191 return min_dist;
192}
193
194/// Struct to hold the coordinates of the four base corners
195/// and the number of intervals N = ntan - 1.
196template < std::floating_point T >
198{
200
201 Vec3 p00; // Coordinates for global index (0, 0)
202 Vec3 p0N; // Coordinates for global index (0, N)
203 Vec3 pN0; // Coordinates for global index (N, 0)
204 Vec3 pNN; // Coordinates for global index (N, N)
205 int N; // Number of intervals = ntan - 1. Must be power of 2.
206
207 // Constructor for convenience (optional)
208 BaseCorners( Vec3 p00_ = {}, Vec3 p0N_ = {}, Vec3 pN0_ = {}, Vec3 pNN_ = {}, int N_ = 0 )
209 : p00( p00_ )
210 , p0N( p0N_ )
211 , pN0( pN0_ )
212 , pNN( pNN_ )
213 , N( N_ )
214 {}
215};
216
217// Memoization cache type: maps (i, j) index pair to computed coordinates
218template < std::floating_point T >
219using MemoizationCache = std::map< std::pair< int, int >, dense::Vec< T, 3 > >;
220
221/// @brief Computes the coordinates for a specific node (i, j) in the final refined grid.
222/// Uses recursion and memoization, sourcing base points from the BaseCorners struct.
223///
224/// @param i Row index (0 to corners.N).
225/// @param j Column index (0 to corners.N).
226/// @param corners Struct containing base corner coordinates and N = ntan - 1.
227/// @param cache Cache to store/retrieve already computed nodes.
228/// @return Vec3 Coordinates of the node (i, j) on the unit sphere.
229///
230template < std::floating_point T >
232{
233 using Vec3 = dense::Vec< T, 3 >;
234
235 // --- Get N and validate indices ---
236 const int N = corners.N;
237 const int ntan = N + 1;
238 if ( i < 0 || i >= ntan || j < 0 || j >= ntan )
239 {
240 throw std::out_of_range( "Requested node index out of range." );
241 }
242 if ( N <= 0 || ( N > 0 && ( N & ( N - 1 ) ) != 0 ) )
243 {
244 throw std::invalid_argument( "BaseCorners.N must be a positive power of 2." );
245 }
246
247 // --- 1. Check Cache ---
248 auto cache_key = std::make_pair( i, j );
249 auto it = cache.find( cache_key );
250 if ( it != cache.end() )
251 {
252 return it->second; // Already computed
253 }
254
255 // --- 2. Base Case: Use BaseCorners struct ---
256 if ( i == 0 && j == 0 )
257 {
258 cache[cache_key] = corners.p00;
259 return corners.p00;
260 }
261 if ( i == 0 && j == N )
262 {
263 cache[cache_key] = corners.p0N;
264 return corners.p0N;
265 }
266 if ( i == N && j == 0 )
267 {
268 cache[cache_key] = corners.pN0;
269 return corners.pN0;
270 }
271 if ( i == N && j == N )
272 {
273 cache[cache_key] = corners.pNN;
274 return corners.pNN;
275 }
276
277 // --- 3. Recursive Step: Find creation level l2 and apply rules ---
278
279 // Find the smallest half-stride l2 (power of 2, starting from 1)
280 // such that (i, j) was NOT present on the grid with stride l = 2*l2.
281 // A point is present if both i and j are multiples of l.
282 int l2 = 1;
283 int l = 2; // l = 2*l2
284 while ( l <= N )
285 { // Iterate through possible creation strides l
286 if ( i % l != 0 || j % l != 0 )
287 {
288 // Found the level 'l' where (i, j) was created.
289 // l2 is l/2.
290 break;
291 }
292 // If execution reaches here, (i, j) exists on grid with stride l.
293 // Check the next finer level.
294 l2 *= 2; // or l2 <<= 1;
295 l = 2 * l2;
296 }
297
298 if ( l > N && l2 == N )
299 { // If loop finished without breaking, l=2N, l2=N
300 // This condition should only be true for the base corners already handled.
301 // If we reach here for non-corner points, something is wrong.
302 throw std::logic_error( "Internal logic error: Failed to find creation level for non-corner point." );
303 }
304
305 Vec3 p1, p2; // Parent points
306
307 // Identify the rule used at creation level l=2*l2, based on relative position
308 if ( i % l == 0 && j % l == l2 )
309 {
310 // Rule 1: Horizontal midpoint ("rows" loop)
311 // i is multiple of l, j is halfway (offset l2)
312 p1 = compute_node_recursive( i, j - l2, corners, cache );
313 p2 = compute_node_recursive( i, j + l2, corners, cache );
314 }
315 else if ( i % l == l2 && j % l == 0 )
316 {
317 // Rule 2: Vertical midpoint ("columns" loop)
318 // j is multiple of l, i is halfway (offset l2)
319 p1 = compute_node_recursive( i - l2, j, corners, cache );
320 p2 = compute_node_recursive( i + l2, j, corners, cache );
321 }
322 else if ( i % l == l2 && j % l == l2 )
323 {
324 // Rule 3: Diagonal midpoint ("diagonals" loop)
325 // Both i and j are halfway (offset l2)
326 p1 = compute_node_recursive( i - l2, j + l2, corners, cache );
327 p2 = compute_node_recursive( i + l2, j - l2, corners, cache );
328 }
329 else
330 {
331 // This should not happen if the logic for finding l is correct and (i,j) is not a base corner.
332 // The checks i%l and j%l should cover all non-zero remainder possibilities correctly.
333 // If i%l==0 and j%l==0, the while loop should have continued.
334 throw std::logic_error( "Internal logic error: Point does not match any creation rule." );
335 }
336
337 // Calculate Euclidean midpoint
338 Vec3 mid = p1 + p2;
339
340 // Normalize to project onto the unit sphere
341 Vec3 result = mid.normalized();
342
343 // --- 4. Store result in cache and return ---
344 cache[cache_key] = result;
345 return result;
346}
347
348/// @brief Generates coordinates for a rectangular subdomain of the refined spherical grid.
349///
350/// @param subdomain_coords_host a properly sized host-allocated view that is filled with the coordinates of the points
351/// @param corners Struct containing the base corner points and N = ntan - 1.
352/// @param i_start_incl Starting row index (inclusive) of the subdomain (global index).
353/// @param i_end_incl Ending row index (inclusive) of the subdomain (global index).
354/// @param j_start_incl Starting column index (inclusive) of the subdomain (global index).
355/// @param j_end_incl Ending column index (inclusive) of the subdomain (global index).
356///
357/// @return Kokkos::View<T**[3], Kokkos::HostSpace> Host view containing coordinates
358/// for the subdomain. Dimensions are ((i_end_incl - 1) - i_start, (j_end_incl - 1) - j_start).
359///
360template < std::floating_point T >
362 const typename Grid3DDataVec< T, 3 >::HostMirror& subdomain_coords_host,
363 int subdomain_idx,
364 const BaseCorners< T >& corners,
365 int i_start_incl,
366 int i_end_incl,
367 int j_start_incl,
368 int j_end_incl )
369{
370 using Vec3 = dense::Vec< T, 3 >;
371
372 const int i_start = i_start_incl;
373 const int j_start = j_start_incl;
374 const int i_end = i_end_incl + 1;
375 const int j_end = j_end_incl + 1;
376
377 // --- Input Validation ---
378 const int N = corners.N;
379 const int ntan = N + 1; // Derive ntan from N in corners struct
380 if ( i_start < 0 || i_end > ntan || i_start >= i_end || j_start < 0 || j_end > ntan || j_start >= j_end )
381 {
382 throw std::invalid_argument( "Invalid subdomain boundaries." );
383 }
384 if ( N <= 0 || ( N > 0 && ( N & ( N - 1 ) ) != 0 ) )
385 {
386 throw std::invalid_argument( "BaseCorners.N must be a positive power of 2." );
387 }
388
389 // --- Initialization ---
390 const size_t subdomain_rows = i_end - i_start;
391 const size_t subdomain_cols = j_end - j_start;
392
393 if ( subdomain_coords_host.extent( 1 ) != subdomain_rows || subdomain_coords_host.extent( 2 ) != subdomain_cols )
394 {
395 Kokkos::abort(
396 "Invalid subdomain dimensions in compute_subdomain(). "
397 "Could be due to having more subdomains than elements. "
398 "But could be something else also..." );
399 }
400
401 MemoizationCache< T > cache; // Each subdomain computation gets its own cache
402
403 // --- Compute nodes within the subdomain ---
404 for ( int i = i_start; i < i_end; ++i )
405 {
406 for ( int j = j_start; j < j_end; ++j )
407 {
408 // Compute the node coordinates using the recursive function
409 Vec3 coords = compute_node_recursive( i, j, corners, cache ); // Pass corners struct
410
411 // Store in the subdomain view (adjusting indices)
412 subdomain_coords_host( subdomain_idx, i - i_start, j - j_start, 0 ) = coords( 0 );
413 subdomain_coords_host( subdomain_idx, i - i_start, j - j_start, 1 ) = coords( 1 );
414 subdomain_coords_host( subdomain_idx, i - i_start, j - j_start, 2 ) = coords( 2 );
415 }
416 }
417}
418
419template < std::floating_point T >
421 const typename Grid3DDataVec< T, 3 >::HostMirror& subdomain_coords_host,
422 int subdomain_idx,
423 int diamond_id,
424 int ntan,
425 int i_start_incl,
426 int i_end_incl,
427 int j_start_incl,
428 int j_end_incl )
429{
430 // Coordinates of the twelve icosahedral nodes of the base grid
431 real_t i_node[12][3];
432
433 // Association of the ten diamonds to the twelve icosahedral nodes
434 //
435 // For each diamond we store the indices of its vertices on the
436 // icosahedral base grid in this map. Ordering: We start with the
437 // pole and proceed in counter-clockwise fashion.
438 int d_node[10][4];
439
440 // -----------------------------------------
441 // Initialise the twelve icosahedral nodes
442 // -----------------------------------------
443
444 // the pentagonal nodes on each "ring" are given in anti-clockwise ordering
445 real_t fifthpi = real_c( 0.4 * std::asin( 1.0 ) );
446 real_t w = real_c( 2.0 * std::acos( 1.0 / ( 2.0 * std::sin( fifthpi ) ) ) );
447 real_t cosw = std::cos( w );
448 real_t sinw = std::sin( w );
449 real_t phi = 0.0;
450
451 // North Pole
452 i_node[0][0] = 0.0;
453 i_node[0][1] = 0.0;
454 i_node[0][2] = +1.0;
455
456 // South Pole
457 i_node[11][0] = 0.0;
458 i_node[11][1] = 0.0;
459 i_node[11][2] = -1.0;
460
461 // upper ring
462 for ( int k = 1; k <= 5; k++ )
463 {
464 phi = real_c( 2.0 ) * ( real_c( k ) - real_c( 0.5 ) ) * fifthpi;
465 i_node[k][0] = sinw * std::cos( phi );
466 i_node[k][1] = sinw * std::sin( phi );
467 i_node[k][2] = cosw;
468 }
469
470 // lower ring
471 for ( int k = 1; k <= 5; k++ )
472 {
473 phi = real_c( 2.0 ) * ( real_c( k ) - 1 ) * fifthpi;
474 i_node[k + 5][0] = sinw * std::cos( phi );
475 i_node[k + 5][1] = sinw * std::sin( phi );
476 i_node[k + 5][2] = -cosw;
477 }
478
479 // ----------------------------------------------
480 // Setup internal index maps for mesh generation
481 // ----------------------------------------------
482
483 // Map icosahedral node indices to diamonds (northern hemisphere)
484 d_node[0][0] = 0;
485 d_node[0][1] = 5;
486 d_node[0][2] = 6;
487 d_node[0][3] = 1;
488 d_node[1][0] = 0;
489 d_node[1][1] = 1;
490 d_node[1][2] = 7;
491 d_node[1][3] = 2;
492 d_node[2][0] = 0;
493 d_node[2][1] = 2;
494 d_node[2][2] = 8;
495 d_node[2][3] = 3;
496 d_node[3][0] = 0;
497 d_node[3][1] = 3;
498 d_node[3][2] = 9;
499 d_node[3][3] = 4;
500 d_node[4][0] = 0;
501 d_node[4][1] = 4;
502 d_node[4][2] = 10;
503 d_node[4][3] = 5;
504
505 // Map icosahedral node indices to diamonds (southern hemisphere)
506 d_node[5][0] = 11;
507 d_node[5][1] = 7;
508 d_node[5][2] = 1;
509 d_node[5][3] = 6;
510 d_node[6][0] = 11;
511 d_node[6][1] = 8;
512 d_node[6][2] = 2;
513 d_node[6][3] = 7;
514 d_node[7][0] = 11;
515 d_node[7][1] = 9;
516 d_node[7][2] = 3;
517 d_node[7][3] = 8;
518 d_node[8][0] = 11;
519 d_node[8][1] = 10;
520 d_node[8][2] = 4;
521 d_node[8][3] = 9;
522 d_node[9][0] = 11;
523 d_node[9][1] = 6;
524 d_node[9][2] = 5;
525 d_node[9][3] = 10;
526
527 // ------------------------
528 // Meshing of unit sphere
529 // ------------------------
530
531 // "left" and "right" w.r.t. d_node depend on hemisphere
532 int L, R;
533 if ( diamond_id < 5 )
534 {
535 L = 1;
536 R = 3;
537 }
538 else
539 {
540 R = 1;
541 L = 3;
542 }
543
544 BaseCorners< T > corners;
545 corners.N = ntan - 1;
546
547 // Insert coordinates of four nodes of this icosahedral diamond for each dim.
548 for ( int i = 0; i < 3; ++i )
549 {
550 corners.p00( i ) = i_node[d_node[diamond_id][0]][i];
551 corners.pN0( i ) = i_node[d_node[diamond_id][L]][i];
552 corners.pNN( i ) = i_node[d_node[diamond_id][2]][i];
553 corners.p0N( i ) = i_node[d_node[diamond_id][R]][i];
554 }
555
556 return compute_subdomain(
557 subdomain_coords_host, subdomain_idx, corners, i_start_incl, i_end_incl, j_start_incl, j_end_incl );
558}
559
560template < std::floating_point T >
562 const typename Grid3DDataVec< T, 3 >::HostMirror& subdomain_coords_host,
563 int subdomain_idx,
564 int diamond_id,
565 int global_refinements,
566 int num_subdomains_per_side,
567 int subdomain_i,
568 int subdomain_j )
569{
570 const auto elements_per_side = 1 << global_refinements;
571 const auto ntan = elements_per_side + 1;
572
573 const auto elements_subdomain_base = elements_per_side / num_subdomains_per_side;
574 const auto elements_remainder = elements_per_side % num_subdomains_per_side;
575
576 const auto elements_in_subdomain_i = elements_subdomain_base + ( subdomain_i < elements_remainder ? 1 : 0 );
577 const auto elements_in_subdomain_j = elements_subdomain_base + ( subdomain_j < elements_remainder ? 1 : 0 );
578
579 const auto start_i = subdomain_i * elements_subdomain_base + std::min( subdomain_i, elements_remainder );
580 const auto start_j = subdomain_j * elements_subdomain_base + std::min( subdomain_j, elements_remainder );
581
582 const auto end_i = start_i + elements_in_subdomain_i;
583 const auto end_j = start_j + elements_in_subdomain_j;
584
585 unit_sphere_single_shell_subdomain_coords< T >(
586 subdomain_coords_host, subdomain_idx, diamond_id, ntan, start_i, end_i, start_j, end_j );
587}
588
589/// @brief (Sortable) Globally unique identifier for a single subdomain of a diamond.
590///
591/// Carries the diamond ID, and the subdomain index (x, y, r) inside the diamond.
592/// Is globally unique (particularly useful for in parallel settings).
593/// Does not carry information about the refinement of a subdomain (just the index).
595{
596 public:
597 /// @brief Creates invalid ID.
599 : diamond_id_( -1 )
600 , subdomain_x_( -1 )
601 , subdomain_y_( -1 )
602 , subdomain_r_( -1 )
603 {}
604
605 /// @brief Creates unique subdomain ID.
607 : diamond_id_( diamond_id )
608 , subdomain_x_( subdomain_x )
609 , subdomain_y_( subdomain_y )
610 , subdomain_r_( subdomain_r )
611 {}
612
613 /// @brief Read from encoded 64-bit integer.
614 ///
615 /// See \ref global_id() for format.
616 explicit SubdomainInfo( const int64_t global_id )
617 : diamond_id_( static_cast< int >( ( global_id >> 57 ) ) )
618 , subdomain_x_( static_cast< int >( ( global_id >> 0 ) & ( ( 1 << 19 ) - 1 ) ) )
619 , subdomain_y_( static_cast< int >( ( global_id >> 19 ) & ( ( 1 << 19 ) - 1 ) ) )
620 , subdomain_r_( static_cast< int >( ( global_id >> 38 ) & ( ( 1 << 19 ) - 1 ) ) )
621 {
622 if ( global_id != this->global_id() )
623 {
624 Kokkos::abort( "Invalid global ID conversion." );
625 }
626 }
627
628 /// @brief Diamond that subdomain is part of.
629 int diamond_id() const { return diamond_id_; }
630
631 /// @brief Subdomain index in lateral x-direction (local to the diamond).
632 int subdomain_x() const { return subdomain_x_; }
633
634 /// @brief Subdomain index in lateral y-direction (local to the diamond).
635 int subdomain_y() const { return subdomain_y_; }
636
637 /// @brief Subdomain index in the radial direction (local to the diamond).
638 int subdomain_r() const { return subdomain_r_; }
639
640 bool operator<( const SubdomainInfo& other ) const
641 {
642 return std::tie( diamond_id_, subdomain_r_, subdomain_y_, subdomain_x_ ) <
643 std::tie( other.diamond_id_, other.subdomain_r_, other.subdomain_y_, other.subdomain_x_ );
644 }
645
646 bool operator==( const SubdomainInfo& other ) const
647 {
648 return std::tie( diamond_id_, subdomain_r_, subdomain_y_, subdomain_x_ ) ==
649 std::tie( other.diamond_id_, other.subdomain_r_, other.subdomain_y_, other.subdomain_x_ );
650 }
651
652 /// @brief Scrambles the four indices (diamond ID, x, y, r) into a single integer.
653 ///
654 /// Format
655 /// @code
656 ///
657 /// bits (LSB) 0-18 (19 bits): subdomain_x
658 /// bits 19-37 (19 bits): subdomain_y
659 /// bits 38-56 (19 bits): subdomain_r
660 /// bits 57-63 (MSB) ( 7 bits): diamond_id (in [0, ..., 9])
661 ///
662 /// @endcode
663 [[nodiscard]] int64_t global_id() const
664 {
665 if ( diamond_id_ >= 10 )
666 {
667 throw std::logic_error( "Diamond ID must be less than 10." );
668 }
669
670 if ( subdomain_x_ > ( 1 << 19 ) - 1 || subdomain_y_ > ( 1 << 19 ) - 1 || subdomain_r_ > ( 1 << 19 ) - 1 )
671 {
672 throw std::logic_error( "Subdomain indices too large." );
673 }
674
675 return ( static_cast< int64_t >( diamond_id_ ) << 57 ) | ( static_cast< int64_t >( subdomain_r_ ) << 38 ) |
676 ( static_cast< int64_t >( subdomain_y_ ) << 19 ) | ( static_cast< int64_t >( subdomain_x_ ) );
677 }
678
679 private:
680 /// Diamond that subdomain is part of.
681 int diamond_id_;
682
683 /// Subdomain index in lateral x-direction (local to the diamond).
684 int subdomain_x_;
685
686 /// Subdomain index in lateral y-direction (local to the diamond).
687 int subdomain_y_;
688
689 /// Subdomain index in radial direction.
690 int subdomain_r_;
691};
692
693inline std::ostream& operator<<( std::ostream& os, const SubdomainInfo& si )
694{
695 os << "Diamond ID: " << si.diamond_id() << ", subdomains (" << si.subdomain_x() << ", " << si.subdomain_y() << ", "
696 << si.subdomain_r() << ")";
697 return os;
698}
699
700using SubdomainToRankDistributionFunction = std::function< mpi::MPIRank( const SubdomainInfo&, const int, const int ) >;
701
702/// @brief Assigns all subdomains to root (rank 0).
704 const SubdomainInfo& subdomain_info,
705 const int num_subdomains_per_diamond_laterally,
706 const int num_subdomains_per_diamond_radially )
707{
708 return 0;
709}
710
711/// @brief Distributes subdomains to ranks as evenly as possible.
712///
713/// Not really sophisticated, just iterates over subdomain indices.
714/// Tries to concentrate ranks in as few diamonds as possible.
715/// But does not optimize surface to volume ratio.
716/// For that we need something like a z- or Hilbert curve.
718 const SubdomainInfo& subdomain_info,
719 const int num_subdomains_per_diamond_laterally,
720 const int num_subdomains_per_diamond_radially )
721{
722 const auto processes = mpi::num_processes();
723
724 const auto subdomains_per_diamond = num_subdomains_per_diamond_laterally * num_subdomains_per_diamond_laterally *
725 num_subdomains_per_diamond_radially;
726
727 const auto subdomains = 10 * subdomains_per_diamond;
728
729 const auto div = subdomains / processes;
730 const auto rem = subdomains % processes;
731
732 const auto subdomain_global_index =
733 subdomain_info.diamond_id() * subdomains_per_diamond +
734 subdomain_info.subdomain_x() * num_subdomains_per_diamond_laterally * num_subdomains_per_diamond_radially +
735 subdomain_info.subdomain_y() * num_subdomains_per_diamond_radially + subdomain_info.subdomain_r();
736
737 if ( subdomain_global_index < ( div + 1 ) * rem )
738 {
739 return subdomain_global_index / ( div + 1 );
740 }
741
742 return rem + ( subdomain_global_index - ( div + 1 ) * rem ) / div;
743}
744
745/// @brief Information about the thick spherical shell mesh.
746///
747/// @note If you want to create a domain for an application, use the \ref DistributedDomain class, which constructs an
748/// instance of this class internally.
749///
750/// **General information**
751///
752/// The thick spherical shell is built from ten spherical diamonds. The diamonds are essentially curved hexahedra.
753/// The number of cells in lateral directions is required to be a power of 2, the number of cells in the radial
754/// direction can be chosen arbitrarily (though a power of two allows for maximally deep multigrid hierarchies).
755///
756/// Each diamond can be subdivided into subdomains (in all three directions) for better parallel distribution (each
757/// process can only operate on one or more entire subdomains).
758///
759/// This class holds data such as
760/// - the shell radii,
761/// - the number of subdomains in each direction (on each diamond),
762/// - the number of nodes per subdomain in each direction (including overlapping nodes where two or more subdomains
763/// meet).
764///
765/// Note that all subdomains always have the same shape.
766///
767/// **Multigrid and coarsening**
768///
769/// Since the global number of cells in a diamond in lateral and radial direction does not need to match, and since
770/// the number of cells in radial direction does not even need to be a power of two (although it is a good idea to
771/// choose it that way), this class computes the maximum number of coarsening steps (which is equivalent to the number
772/// of "refinement levels") dynamically. Thus, a bad choice for the number of radial layers may result in a mesh that
773/// cannot be coarsened at all.
774///
775/// **Parallel distribution**
776///
777/// This class has no notion of parallel distribution. For that refer to the \ref DistributedDomain class.
778///
780{
781 public:
782 DomainInfo() = default;
783
784 /// @brief Constructs a thick spherical shell with one subdomain per diamond (10 subdomains total) and shells at
785 /// specific radii.
786 ///
787 /// @note Use `uniform_shell_radii()` to quickly construct a uniform list of radii.
788 ///
789 /// Note: a 'shell' is a spherical 2D manifold in 3D space (it is thin),
790 /// a 'layer' is defined as the volume between two 'shells' (it is thick)
791 ///
792 /// @param diamond_lateral_refinement_level number of lateral diamond refinements
793 /// @param radii list of shell radii, vector must have at least 2 elements
794 DomainInfo( int diamond_lateral_refinement_level, const std::vector< double >& radii )
796 {}
797
798 /// @brief Constructs a thick spherical shell with shells at specific radii.
799 ///
800 /// @note Use `uniform_shell_radii()` to quickly construct a uniform list of radii.
801 ///
802 /// Note: a 'shell' is a spherical 2D manifold in 3D space (it is thin),
803 /// a 'layer' is defined as the volume between two 'shells' (it is thick)
804 ///
805 /// @param diamond_lateral_refinement_level number of lateral diamond refinements
806 /// @param radii list of shell radii, vector must have at least 2 elements
807 /// @param num_subdomains_in_lateral_direction number of subdomains in lateral direction per diamond
808 /// @param num_subdomains_in_radial_direction number of subdomains in radial direction per diamond
811 const std::vector< double >& radii,
812 int num_subdomains_in_lateral_direction,
814 : diamond_lateral_refinement_level_( diamond_lateral_refinement_level )
815 , radii_( radii )
816 , num_subdomains_in_lateral_direction_( num_subdomains_in_lateral_direction )
817 , num_subdomains_in_radial_direction_( num_subdomains_in_radial_direction )
818 {
819 const int num_layers = static_cast< int >( radii.size() ) - 1;
820 if ( num_layers % num_subdomains_in_radial_direction_ != 0 )
821 {
822 throw std::invalid_argument(
823 "Number of layers must be divisible by number of subdomains in radial direction. "
824 "You have requested " +
825 std::to_string( num_layers ) + " layers (" + std::to_string( radii.size() ) + " shells), and " +
826 std::to_string( num_subdomains_in_radial_direction ) + " radial subdomains." );
827 }
828 }
829
830 /// @brief The "maximum refinement level" of the subdomains.
831 ///
832 /// This (non-negative) number is essentially indicating how many times a subdomain can be uniformly coarsened.
834 {
835 const auto max_refinement_level_lat =
836 std::countr_zero( static_cast< unsigned >( subdomain_num_nodes_per_side_laterally() - 1 ) );
837 const auto max_refinement_level_rad =
838 std::countr_zero( static_cast< unsigned >( subdomain_num_nodes_radially() - 1 ) );
839
840 return std::min( max_refinement_level_lat, max_refinement_level_rad );
841 }
842
843 int diamond_lateral_refinement_level() const { return diamond_lateral_refinement_level_; }
844
845 const std::vector< double >& radii() const { return radii_; }
846
847 int num_subdomains_per_diamond_side() const { return num_subdomains_in_lateral_direction_; }
848
849 int num_subdomains_in_radial_direction() const { return num_subdomains_in_radial_direction_; }
850
851 /// @brief Equivalent to calling subdomain_num_nodes_per_side_laterally( subdomain_refinement_level() )
853 {
854 const int num_cells_per_diamond_side = 1 << diamond_lateral_refinement_level();
855 const int num_cells_per_subdomain_side = num_cells_per_diamond_side / num_subdomains_per_diamond_side();
856 const int num_nodes_per_subdomain_side = num_cells_per_subdomain_side + 1;
857 return num_nodes_per_subdomain_side;
858 }
859
860 /// @brief Equivalent to calling subdomain_num_nodes_radially( subdomain_refinement_level() )
862 {
863 const int num_layers = radii_.size() - 1;
864 const int num_layers_per_subdomain = num_layers / num_subdomains_in_radial_direction_;
865 return num_layers_per_subdomain + 1;
866 }
867
868 /// @brief Number of nodes in the lateral direction of a subdomain on the passed level.
869 ///
870 /// The level must be non-negative. The finest level is given by subdomain_max_refinement_level().
871 int subdomain_num_nodes_per_side_laterally( const int level ) const
872 {
873 if ( level < 0 )
874 {
875 throw std::invalid_argument( "Level must be non-negative." );
876 }
877
878 if ( level > subdomain_max_refinement_level() )
879 {
880 throw std::invalid_argument( "Level must be less than or equal to max subdomain refinement level." );
881 }
882
883 const int coarsening_steps = subdomain_max_refinement_level() - level;
884 return ( ( subdomain_num_nodes_per_side_laterally() - 1 ) >> coarsening_steps ) + 1;
885 }
886
887 /// @brief Number of nodes in the radial direction of a subdomain on the passed level.
888 ///
889 /// The level must be non-negative. The finest level is given by subdomain_max_refinement_level().
890 int subdomain_num_nodes_radially( const int level ) const
891 {
892 if ( level < 0 )
893 {
894 throw std::invalid_argument( "Level must be non-negative." );
895 }
896
897 if ( level > subdomain_max_refinement_level() )
898 {
899 throw std::invalid_argument( "Level must be less than or equal to subdomain refinement level." );
900 }
901
902 const int coarsening_steps = subdomain_max_refinement_level() - level;
903 return ( ( subdomain_num_nodes_radially() - 1 ) >> coarsening_steps ) + 1;
904 }
905
906 std::vector< SubdomainInfo > local_subdomains( const SubdomainToRankDistributionFunction& subdomain_to_rank ) const
907 {
908 const auto rank = mpi::rank();
909
910 std::vector< SubdomainInfo > subdomains;
911 for ( int diamond_id = 0; diamond_id < 10; diamond_id++ )
912 {
913 for ( int x = 0; x < num_subdomains_per_diamond_side(); x++ )
914 {
915 for ( int y = 0; y < num_subdomains_per_diamond_side(); y++ )
916 {
917 for ( int r = 0; r < num_subdomains_in_radial_direction_; r++ )
918 {
919 SubdomainInfo subdomain( diamond_id, x, y, r );
920
921 const auto target_rank = subdomain_to_rank(
923
924 if ( target_rank == rank )
925 {
926 subdomains.push_back( subdomain );
927 }
928 }
929 }
930 }
931 }
932
933 if ( subdomains.empty() )
934 {
935 throw std::logic_error( "No local subdomains found on rank " + std::to_string( rank ) + "." );
936 }
937
938 return subdomains;
939 }
940
941 private:
942 /// Number of times each diamond is refined laterally in each direction.
943 int diamond_lateral_refinement_level_{};
944
945 /// Shell radii.
946 std::vector< double > radii_;
947
948 /// Number of subdomains per diamond (for parallel partitioning) in the lateral direction (at least 1).
949 int num_subdomains_in_lateral_direction_{};
950
951 /// Number of subdomains per diamond (for parallel partitioning) in the radial direction (at least 1).
952 int num_subdomains_in_radial_direction_{};
953};
954
955/// @brief Neighborhood information of a single subdomain.
956///
957/// @note If you want to create a domain for an application, use the \ref terra::grid::shell::DistributedDomain class,
958/// which constructs an instance of this class internally.
959///
960/// Holds information such as the MPI ranks of the neighboring subdomains, and their orientation.
961/// Required for communication (packing, unpacking, sending, receiving 'ghost-layer' data).
962///
963/// **Details on communication**
964///
965/// Data is rotated during unpacking.
966///
967/// *Packing/sending*
968///
969/// Sender just packs data from the grid into a buffer using the (x, y, r) coordinates in order.
970/// For instance: the face boundary xr is packed into a 2D buffer: buffer( x, r ), the face boundary yr is packed
971/// as buffer( y, r ), always iterating locally from 0 to end.
972///
973/// *Unpacking*
974///
975/// When a packet from a certain neighbor subdomain arrives, we have the following information
976/// set up in this class (for instance in the `NeighborSubdomainTupleFace` instances):
977///
978/// *Organization*
979///
980/// At each boundary face of a local subdomain we store in this class a list of tuples with entries:
981///
982/// SubdomainInfo: neighboring subdomain identifier
983/// BoundaryFace: boundary face of the neighboring subdomain (from its local view)
984/// UnpackingOrderingFace: information how to iterate over the buffer for each coordinate during unpacking
985///
986/// So e.g., the data (for some subdomain):
987///
988/// @code
989/// neighborhood_face_[ F_X1R ] = { neighbor_subdomain_info, F_1YR, (BACKWARD, FORWARD), neighbor_rank }
990/// @endcode
991///
992/// means that for this subdomain, the boundary face `F_X1R` interfaces with the neighbor subdomain
993/// `neighbor_subdomain_info`, at its boundary `F_1YR`, that is located on rank `neighbor_rank`.
994/// If we unpack data that we receive from the subdomain, we must invert the iteration over the first
995/// buffer index, and move forward in the second index.
996///
997/// @note See \ref terra::grid::BoundaryVertex, \ref terra::grid::BoundaryEdge, \ref terra::grid::BoundaryFace,
998/// \ref terra::grid::BoundaryDirection for details on the naming convention of the boundary types like `F_X1R`.
999/// Roughly: `0 == start`, `1 == end`, `X == varying in x`, `Y == varying in y`, `R == varying in r`.
1000///
1001/// *Execution*
1002///
1003/// Let's assume we receive data from the neighbor specified above.
1004///
1005/// Sender side (with local boundary `F_1YR`):
1006///
1007/// @code
1008/// buffer( y, r ) = send_data( sender_local_subdomain_id, x_size - 1, y, r )
1009/// ^^^^^^^^^^
1010/// == x_end
1011/// == const
1012/// @endcode
1013///
1014/// Receiver side (with local boundary `F_X1R`):
1015///
1016/// @code
1017/// recv_data( receiver_local_subdomain_id, x, y_size - 1, r ) = buffer( x_size - 1 - x, r )
1018/// ^^^^^^^^^^ ^^^^^^^^^^^^^^ ^
1019/// == y_end BACKWARD FORWARD
1020/// == const
1021/// @endcode
1022///
1023/// @note It is due to the structure of the spherical shell mesh that we never have to swap the indices!
1024/// (For vertex boundaries this is anyway not required (0D array) and for edges neither (1D array - we only
1025/// have forward and backward iteration).)
1026/// Thus, it is enough to have the FORWARD/BACKWARD tuple! The radial direction is always radial.
1027/// And if we communicate in the radial direction (i.e., sending "xy-planes") then we never need to swap since
1028/// we are in the same diamond.
1029///
1031{
1032 public:
1034 using UnpackOrderingFace = std::tuple< BoundaryDirection, BoundaryDirection >;
1035
1036 using NeighborSubdomainTupleVertex = std::tuple< SubdomainInfo, BoundaryVertex, mpi::MPIRank >;
1037 using NeighborSubdomainTupleEdge = std::tuple< SubdomainInfo, BoundaryEdge, UnpackOrderingEdge, mpi::MPIRank >;
1038 using NeighborSubdomainTupleFace = std::tuple< SubdomainInfo, BoundaryFace, UnpackOrderingFace, mpi::MPIRank >;
1039
1041
1043 const DomainInfo& domain_info,
1044 const SubdomainInfo& subdomain_info,
1045 const SubdomainToRankDistributionFunction& subdomain_to_rank )
1046 {
1047 setup_neighborhood( domain_info, subdomain_info, subdomain_to_rank );
1048 }
1049
1050 const std::map< BoundaryVertex, std::vector< NeighborSubdomainTupleVertex > >& neighborhood_vertex() const
1051 {
1052 return neighborhood_vertex_;
1053 }
1054
1055 const std::map< BoundaryEdge, std::vector< NeighborSubdomainTupleEdge > >& neighborhood_edge() const
1056 {
1057 return neighborhood_edge_;
1058 }
1059
1060 const std::map< BoundaryFace, NeighborSubdomainTupleFace >& neighborhood_face() const { return neighborhood_face_; }
1061
1062 private:
1063 void setup_neighborhood_faces( const DomainInfo& domain_info, const SubdomainInfo& subdomain_info )
1064 {
1065 const mpi::MPIRank rank_will_be_overwritten_later = -1;
1066
1067 const int diamond_id = subdomain_info.diamond_id();
1068 const int subdomain_x = subdomain_info.subdomain_x();
1069 const int subdomain_y = subdomain_info.subdomain_y();
1070 const int subdomain_r = subdomain_info.subdomain_r();
1071
1072 const int num_lateral_subdomains = domain_info.num_subdomains_per_diamond_side();
1073 const int num_radial_subdomains = domain_info.num_subdomains_in_radial_direction();
1074
1075 for ( const auto boundary_face : all_boundary_faces )
1076 {
1077 const bool diamond_diamond_boundary =
1078 ( boundary_face == BoundaryFace::F_0YR && subdomain_x == 0 ) ||
1079 ( boundary_face == BoundaryFace::F_1YR && subdomain_x == num_lateral_subdomains - 1 ) ||
1080 ( boundary_face == BoundaryFace::F_X0R && subdomain_y == 0 ) ||
1081 ( boundary_face == BoundaryFace::F_X1R && subdomain_y == num_lateral_subdomains - 1 );
1082
1083 if ( diamond_diamond_boundary )
1084 {
1085 // Node equivalences: part one - communication between diamonds at the same poles
1086 // Iterating forward laterally.
1087
1088 // d_0( 0, :, r ) = d_1( :, 0, r )
1089 // d_1( 0, :, r ) = d_2( :, 0, r )
1090 // d_2( 0, :, r ) = d_3( :, 0, r )
1091 // d_3( 0, :, r ) = d_4( :, 0, r )
1092 // d_4( 0, :, r ) = d_0( :, 0, r )
1093
1094 // d_5( 0, :, r ) = d_6( :, 0, r )
1095 // d_6( 0, :, r ) = d_7( :, 0, r )
1096 // d_7( 0, :, r ) = d_8( :, 0, r )
1097 // d_8( 0, :, r ) = d_9( :, 0, r )
1098 // d_9( 0, :, r ) = d_5( :, 0, r )
1099
1100 // Node equivalences: part two - communication between diamonds at different poles
1101 // Need to go backwards laterally.
1102
1103 // d_0( :, end, r ) = d_5( end, :, r )
1104 // d_1( :, end, r ) = d_6( end, :, r )
1105 // d_2( :, end, r ) = d_7( end, :, r )
1106 // d_3( :, end, r ) = d_8( end, :, r )
1107 // d_4( :, end, r ) = d_9( end, :, r )
1108
1109 // d_5( :, end, r ) = d_1( end, :, r )
1110 // d_6( :, end, r ) = d_2( end, :, r )
1111 // d_7( :, end, r ) = d_3( end, :, r )
1112 // d_8( :, end, r ) = d_4( end, :, r )
1113 // d_9( :, end, r ) = d_0( end, :, r )
1114
1115 switch ( diamond_id )
1116 {
1117 case 0:
1118 case 1:
1119 case 2:
1120 case 3:
1121 case 4:
1122
1123 switch ( boundary_face )
1124 {
1125 // (north-north)
1127 neighborhood_face_[BoundaryFace::F_0YR] = {
1128 SubdomainInfo( ( diamond_id + 1 ) % 5, subdomain_y, 0, subdomain_r ),
1131 rank_will_be_overwritten_later };
1132 break;
1134 neighborhood_face_[BoundaryFace::F_X0R] = {
1135 SubdomainInfo( ( diamond_id + 4 ) % 5, 0, subdomain_x, subdomain_r ),
1138 rank_will_be_overwritten_later };
1139 break;
1140
1141 // (north-south)
1143 neighborhood_face_[BoundaryFace::F_X1R] = {
1144 SubdomainInfo(
1145 diamond_id + 5,
1146 num_lateral_subdomains - 1,
1147 num_lateral_subdomains - 1 - subdomain_x,
1148 subdomain_r ),
1151 rank_will_be_overwritten_later };
1152 break;
1154 neighborhood_face_[BoundaryFace::F_1YR] = {
1155 SubdomainInfo(
1156 ( diamond_id + 4 ) % 5 + 5,
1157 num_lateral_subdomains - 1 - subdomain_y,
1158 num_lateral_subdomains - 1,
1159 subdomain_r ),
1162 rank_will_be_overwritten_later };
1163 break;
1164
1165 default:
1166 Kokkos::abort( "This should not happen." );
1167 }
1168
1169 break;
1170
1171 case 5:
1172 case 6:
1173 case 7:
1174 case 8:
1175 case 9:
1176 switch ( boundary_face )
1177 {
1178 // (south-south)
1180 neighborhood_face_[BoundaryFace::F_0YR] = {
1181 SubdomainInfo( ( diamond_id + 1 ) % 5 + 5, subdomain_y, 0, subdomain_r ),
1184 rank_will_be_overwritten_later };
1185 break;
1187 neighborhood_face_[BoundaryFace::F_X0R] = {
1188 SubdomainInfo( ( diamond_id - 1 ) % 5 + 5, 0, subdomain_x, subdomain_r ),
1191 rank_will_be_overwritten_later };
1192 break;
1193
1194 // (south-north)
1196 neighborhood_face_[BoundaryFace::F_X1R] = {
1197 SubdomainInfo(
1198 ( diamond_id - 4 ) % 5,
1199 num_lateral_subdomains - 1,
1200 num_lateral_subdomains - 1 - subdomain_x,
1201 subdomain_r ),
1204 rank_will_be_overwritten_later };
1205 break;
1207 neighborhood_face_[BoundaryFace::F_1YR] = {
1208 SubdomainInfo(
1209 diamond_id - 5,
1210 num_lateral_subdomains - 1 - subdomain_y,
1211 num_lateral_subdomains - 1,
1212 subdomain_r ),
1215 rank_will_be_overwritten_later };
1216 break;
1217 default:
1218 Kokkos::abort( "This should not happen." );
1219 }
1220 break;
1221
1222 default:
1223 Kokkos::abort( "Invalid diamond id." );
1224 }
1225 }
1226 else
1227 {
1228 // Same diamond.
1229
1230 switch ( boundary_face )
1231 {
1233 neighborhood_face_[boundary_face] = {
1234 SubdomainInfo( diamond_id, subdomain_x - 1, subdomain_y, subdomain_r ),
1237 rank_will_be_overwritten_later };
1238 break;
1240 neighborhood_face_[boundary_face] = {
1241 SubdomainInfo( diamond_id, subdomain_x + 1, subdomain_y, subdomain_r ),
1244 rank_will_be_overwritten_later };
1245 break;
1247 neighborhood_face_[boundary_face] = {
1248 SubdomainInfo( diamond_id, subdomain_x, subdomain_y - 1, subdomain_r ),
1251 rank_will_be_overwritten_later };
1252 break;
1254 neighborhood_face_[boundary_face] = {
1255 SubdomainInfo( diamond_id, subdomain_x, subdomain_y + 1, subdomain_r ),
1258 rank_will_be_overwritten_later };
1259 break;
1261 if ( subdomain_r > 0 )
1262 {
1263 neighborhood_face_[boundary_face] = {
1264 SubdomainInfo( diamond_id, subdomain_x, subdomain_y, subdomain_r - 1 ),
1267 rank_will_be_overwritten_later };
1268 }
1269 break;
1271 if ( subdomain_r < num_radial_subdomains - 1 )
1272 {
1273 neighborhood_face_[boundary_face] = {
1274 SubdomainInfo( diamond_id, subdomain_x, subdomain_y, subdomain_r + 1 ),
1277 rank_will_be_overwritten_later };
1278 }
1279 break;
1280 }
1281 }
1282 }
1283 }
1284
1285 void setup_neighborhood_edges( const DomainInfo& domain_info, const SubdomainInfo& subdomain_info )
1286 {
1287 const int diamond_id = subdomain_info.diamond_id();
1288 const int subdomain_x = subdomain_info.subdomain_x();
1289 const int subdomain_y = subdomain_info.subdomain_y();
1290 const int subdomain_r = subdomain_info.subdomain_r();
1291
1292 const int num_lateral_subdomains = domain_info.num_subdomains_per_diamond_side();
1293 const int num_radial_subdomains = domain_info.num_subdomains_in_radial_direction();
1294
1295 for ( const auto boundary_edge : all_boundary_edges )
1296 {
1297 // Edges in radial direction (beyond diamond boundaries).
1298
1299 if ( diamond_id >= 0 && diamond_id <= 4 )
1300 {
1301 if ( boundary_edge == BoundaryEdge::E_00R )
1302 {
1303 if ( subdomain_x == 0 && subdomain_y == 0 )
1304 {
1305 // North pole
1306 neighborhood_edge_[boundary_edge].emplace_back(
1307 SubdomainInfo( ( diamond_id + 2 ) % 5, 0, 0, subdomain_r ),
1310 -1 );
1311 neighborhood_edge_[boundary_edge].emplace_back(
1312 SubdomainInfo( ( diamond_id + 3 ) % 5, 0, 0, subdomain_r ),
1315 -1 );
1316 }
1317 else if ( subdomain_x == 0 && subdomain_y > 0 )
1318 {
1319 // Northern hemisphere to the right.
1320 neighborhood_edge_[boundary_edge].emplace_back(
1321 SubdomainInfo( ( diamond_id + 1 ) % 5, subdomain_y - 1, 0, subdomain_r ),
1324 -1 );
1325 }
1326 else if ( subdomain_x > 0 && subdomain_y == 0 )
1327 {
1328 // Northern hemisphere to the left.
1329 neighborhood_edge_[boundary_edge].emplace_back(
1330 SubdomainInfo( ( diamond_id + 4 ) % 5, 0, subdomain_x - 1, subdomain_r ),
1333 -1 );
1334 }
1335 }
1336 else if ( boundary_edge == BoundaryEdge::E_01R )
1337 {
1338 if ( subdomain_x == 0 && subdomain_y < num_lateral_subdomains - 1 )
1339 {
1340 // Northern hemisphere to the right
1341 neighborhood_edge_[boundary_edge].emplace_back(
1342 SubdomainInfo( ( diamond_id + 1 ) % 5, subdomain_y + 1, 0, subdomain_r ),
1345 -1 );
1346 }
1347 else if ( subdomain_x > 0 && subdomain_y == num_lateral_subdomains - 1 )
1348 {
1349 // Northern hemisphere to the right and southern
1350 neighborhood_edge_[boundary_edge].emplace_back(
1351 SubdomainInfo(
1352 diamond_id + 5,
1353 num_lateral_subdomains - 1,
1354 num_lateral_subdomains - subdomain_x,
1355 subdomain_r ),
1358 -1 );
1359 }
1360 }
1361 else if ( boundary_edge == BoundaryEdge::E_10R )
1362 {
1363 if ( subdomain_y == 0 && subdomain_x < num_lateral_subdomains - 1 )
1364 {
1365 // Northern hemisphere to the left
1366 neighborhood_edge_[boundary_edge].emplace_back(
1367 SubdomainInfo( ( diamond_id + 4 ) % 5, 0, subdomain_x + 1, subdomain_r ),
1370 -1 );
1371 }
1372 else if ( subdomain_y > 0 && subdomain_x == num_lateral_subdomains - 1 )
1373 {
1374 // Northern hemisphere to the left and southern
1375 neighborhood_edge_[boundary_edge].emplace_back(
1376 SubdomainInfo(
1377 ( diamond_id + 4 ) % 5 + 5,
1378 num_lateral_subdomains - subdomain_y,
1379 num_lateral_subdomains - 1,
1380 subdomain_r ),
1383 -1 );
1384 }
1385 }
1386 else if ( boundary_edge == BoundaryEdge::E_11R )
1387 {
1388 if ( subdomain_x < num_lateral_subdomains - 1 && subdomain_y == num_lateral_subdomains - 1 )
1389 {
1390 // Northern hemi to southern (right)
1391 neighborhood_edge_[boundary_edge].emplace_back(
1392 SubdomainInfo(
1393 diamond_id + 5,
1394 num_lateral_subdomains - 1,
1395 num_lateral_subdomains - subdomain_x - 2,
1396 subdomain_r ),
1399 -1 );
1400 }
1401 else if ( subdomain_y < num_lateral_subdomains - 1 && subdomain_x == num_lateral_subdomains - 1 )
1402 {
1403 // Northern hemi to southern (left)
1404 neighborhood_edge_[boundary_edge].emplace_back(
1405 SubdomainInfo(
1406 ( diamond_id + 4 ) % 5 + 5,
1407 num_lateral_subdomains - subdomain_y - 2,
1408 num_lateral_subdomains - 1,
1409 subdomain_r ),
1412 -1 );
1413 }
1414 }
1415 }
1416 else if ( diamond_id >= 5 && diamond_id <= 9 )
1417 {
1418 if ( boundary_edge == BoundaryEdge::E_00R )
1419 {
1420 if ( subdomain_x == 0 && subdomain_y == 0 )
1421 {
1422 // South pole
1423 neighborhood_edge_[boundary_edge].emplace_back(
1424 SubdomainInfo( ( diamond_id + 2 ) % 5 + 5, 0, 0, subdomain_r ),
1427 -1 );
1428 neighborhood_edge_[boundary_edge].emplace_back(
1429 SubdomainInfo( ( diamond_id + 3 ) % 5 + 5, 0, 0, subdomain_r ),
1432 -1 );
1433 }
1434
1435 if ( subdomain_x == 0 && subdomain_y > 0 )
1436 {
1437 // Southern hemisphere to the right.
1438 neighborhood_edge_[boundary_edge].emplace_back(
1439 SubdomainInfo( ( diamond_id + 1 ) % 5 + 5, subdomain_y - 1, 0, subdomain_r ),
1442 -1 );
1443 }
1444 else if ( subdomain_x > 0 && subdomain_y == 0 )
1445 {
1446 // Southern hemisphere to the left.
1447 neighborhood_edge_[boundary_edge].emplace_back(
1448 SubdomainInfo( ( diamond_id + 4 ) % 5 + 5, 0, subdomain_x - 1, subdomain_r ),
1451 -1 );
1452 }
1453 }
1454
1455 if ( boundary_edge == BoundaryEdge::E_01R )
1456 {
1457 if ( subdomain_x == 0 && subdomain_y < num_lateral_subdomains - 1 )
1458 {
1459 // Southern hemisphere to the right
1460 neighborhood_edge_[boundary_edge].push_back(
1461 { SubdomainInfo( ( diamond_id + 1 ) % 5 + 5, subdomain_y + 1, 0, subdomain_r ),
1464 -1 } );
1465 }
1466 else if ( subdomain_x > 0 && subdomain_y == num_lateral_subdomains - 1 )
1467 {
1468 // Southern hemisphere to the right and north
1469 neighborhood_edge_[boundary_edge].push_back(
1470 { SubdomainInfo(
1471 ( diamond_id + 1 ) % 5,
1472 num_lateral_subdomains - 1,
1473 num_lateral_subdomains - subdomain_x,
1474 subdomain_r ),
1477 -1 } );
1478 }
1479 }
1480
1481 if ( boundary_edge == BoundaryEdge::E_10R )
1482 {
1483 if ( subdomain_y == 0 && subdomain_x < num_lateral_subdomains - 1 )
1484 {
1485 // Southern hemisphere to the left
1486 neighborhood_edge_[boundary_edge].emplace_back(
1487 SubdomainInfo( ( diamond_id - 1 ) % 5 + 5, 0, subdomain_x + 1, subdomain_r ),
1490 -1 );
1491 }
1492 else if ( subdomain_y > 0 && subdomain_x == num_lateral_subdomains - 1 )
1493 {
1494 // Southern hemisphere to the left and northern
1495 neighborhood_edge_[boundary_edge].emplace_back(
1496 SubdomainInfo(
1497 diamond_id - 5,
1498 num_lateral_subdomains - subdomain_y,
1499 num_lateral_subdomains - 1,
1500 subdomain_r ),
1503 -1 );
1504 }
1505 }
1506
1507 if ( boundary_edge == BoundaryEdge::E_11R )
1508 {
1509 if ( subdomain_x < num_lateral_subdomains - 1 && subdomain_y == num_lateral_subdomains - 1 )
1510 {
1511 // Southern hemi to northern (right)
1512 neighborhood_edge_[boundary_edge].emplace_back(
1513 SubdomainInfo(
1514 ( diamond_id - 4 ) % 5,
1515 num_lateral_subdomains - 1,
1516 num_lateral_subdomains - subdomain_x - 2,
1517 subdomain_r ),
1520 -1 );
1521 }
1522 else if ( subdomain_y < num_lateral_subdomains - 1 && subdomain_x == num_lateral_subdomains - 1 )
1523 {
1524 // Southern hemi to northern (left)
1525 neighborhood_edge_[boundary_edge].emplace_back(
1526 SubdomainInfo(
1527 diamond_id - 5,
1528 num_lateral_subdomains - subdomain_y - 2,
1529 num_lateral_subdomains - 1,
1530 subdomain_r ),
1533 -1 );
1534 }
1535 }
1536 }
1537 else
1538 {
1539 Kokkos::abort( "Invalid diamond ID." );
1540 }
1541
1542 // Still beyond diamond boundaries but now lateral edges
1543
1544 if ( diamond_id >= 0 && diamond_id <= 4 )
1545 {
1546 if ( boundary_edge == BoundaryEdge::E_0Y0 && subdomain_x == 0 && subdomain_r > 0 )
1547 {
1548 // Northern hemi to the right + cmb
1549 neighborhood_edge_[boundary_edge].emplace_back(
1550 SubdomainInfo( ( diamond_id + 1 ) % 5, subdomain_y, 0, subdomain_r - 1 ),
1553 -1 );
1554 }
1555 else if (
1556 boundary_edge == BoundaryEdge::E_0Y1 && subdomain_x == 0 &&
1557 subdomain_r < num_radial_subdomains - 1 )
1558 {
1559 // Northern hemi to the right + surface
1560 neighborhood_edge_[boundary_edge].emplace_back(
1561 SubdomainInfo( ( diamond_id + 1 ) % 5, subdomain_y, 0, subdomain_r + 1 ),
1564 -1 );
1565 }
1566 else if ( boundary_edge == BoundaryEdge::E_X00 && subdomain_y == 0 && subdomain_r > 0 )
1567 {
1568 // Northern hemi to the left + cmb
1569 neighborhood_edge_[boundary_edge].emplace_back(
1570 SubdomainInfo( ( diamond_id + 4 ) % 5, 0, subdomain_x, subdomain_r - 1 ),
1573 -1 );
1574 }
1575 else if (
1576 boundary_edge == BoundaryEdge::E_X01 && subdomain_y == 0 &&
1577 subdomain_r < num_radial_subdomains - 1 )
1578 {
1579 // Northern hemi to the left + surface
1580 neighborhood_edge_[boundary_edge].emplace_back(
1581 SubdomainInfo( ( diamond_id + 4 ) % 5, 0, subdomain_x, subdomain_r + 1 ),
1584 -1 );
1585 }
1586
1587 // Northern to southern
1588 if ( boundary_edge == BoundaryEdge::E_X10 && subdomain_y == num_lateral_subdomains - 1 &&
1589 subdomain_r > 0 )
1590 {
1591 // Northern hemi to the bottom right + cmb
1592 neighborhood_edge_[boundary_edge].emplace_back(
1593 SubdomainInfo(
1594 diamond_id + 5,
1595 num_lateral_subdomains - 1,
1596 num_lateral_subdomains - 1 - subdomain_x,
1597 subdomain_r - 1 ),
1600 -1 );
1601 }
1602 else if (
1603 boundary_edge == BoundaryEdge::E_X11 && subdomain_y == num_lateral_subdomains - 1 &&
1604 subdomain_r < num_radial_subdomains - 1 )
1605 {
1606 // Northern hemi to the bottom right + surface
1607 neighborhood_edge_[boundary_edge].emplace_back(
1608 SubdomainInfo(
1609 diamond_id + 5,
1610 num_lateral_subdomains - 1,
1611 num_lateral_subdomains - 1 - subdomain_x,
1612 subdomain_r + 1 ),
1615 -1 );
1616 }
1617 else if (
1618 boundary_edge == BoundaryEdge::E_1Y0 && subdomain_x == num_lateral_subdomains - 1 &&
1619 subdomain_r > 0 )
1620 {
1621 // Northern hemi to the bottom left + cmb
1622 neighborhood_edge_[boundary_edge].emplace_back(
1623 SubdomainInfo(
1624 ( diamond_id + 4 ) % 5 + 5,
1625 num_lateral_subdomains - 1 - subdomain_y,
1626 num_lateral_subdomains - 1,
1627 subdomain_r - 1 ),
1630 -1 );
1631 }
1632 else if (
1633 boundary_edge == BoundaryEdge::E_1Y1 && subdomain_x == num_lateral_subdomains - 1 &&
1634 subdomain_r < num_radial_subdomains - 1 )
1635 {
1636 // Northern hemi to the bottom left + surface
1637 neighborhood_edge_[boundary_edge].emplace_back(
1638 SubdomainInfo(
1639 ( diamond_id + 4 ) % 5 + 5,
1640 num_lateral_subdomains - 1 - subdomain_y,
1641 num_lateral_subdomains - 1,
1642 subdomain_r + 1 ),
1645 -1 );
1646 }
1647 }
1648 else if ( diamond_id >= 5 && diamond_id <= 9 )
1649 {
1650 if ( boundary_edge == BoundaryEdge::E_0Y0 && subdomain_x == 0 && subdomain_r > 0 )
1651 {
1652 // Southern hemi to the right + cmb
1653 neighborhood_edge_[boundary_edge].emplace_back(
1654 SubdomainInfo( ( diamond_id + 1 ) % 5 + 5, subdomain_y, 0, subdomain_r - 1 ),
1657 -1 );
1658 }
1659 else if (
1660 boundary_edge == BoundaryEdge::E_0Y1 && subdomain_x == 0 &&
1661 subdomain_r < num_radial_subdomains - 1 )
1662 {
1663 // Southern hemi to the right + surface
1664 neighborhood_edge_[boundary_edge].emplace_back(
1665 SubdomainInfo( ( diamond_id + 1 ) % 5 + 5, subdomain_y, 0, subdomain_r + 1 ),
1668 -1 );
1669 }
1670 else if ( boundary_edge == BoundaryEdge::E_X00 && subdomain_y == 0 && subdomain_r > 0 )
1671 {
1672 // Southern hemi to the left + cmb
1673 neighborhood_edge_[boundary_edge].emplace_back(
1674 SubdomainInfo( ( diamond_id - 1 ) % 5 + 5, 0, subdomain_x, subdomain_r - 1 ),
1677 -1 );
1678 }
1679 else if (
1680 boundary_edge == BoundaryEdge::E_X01 && subdomain_y == 0 &&
1681 subdomain_r < num_radial_subdomains - 1 )
1682 {
1683 // Southern hemi to the left + surface
1684 neighborhood_edge_[boundary_edge].emplace_back(
1685 SubdomainInfo( ( diamond_id - 1 ) % 5 + 5, 0, subdomain_x, subdomain_r + 1 ),
1688 -1 );
1689 }
1690
1691 // Southern to northern
1692 if ( boundary_edge == BoundaryEdge::E_X10 && subdomain_y == num_lateral_subdomains - 1 &&
1693 subdomain_r > 0 )
1694 {
1695 // Southern hemi to the top right + cmb
1696 neighborhood_edge_[boundary_edge].emplace_back(
1697 SubdomainInfo(
1698 ( diamond_id - 4 ) % 5,
1699 num_lateral_subdomains - 1,
1700 num_lateral_subdomains - 1 - subdomain_x,
1701 subdomain_r - 1 ),
1704 -1 );
1705 }
1706 else if (
1707 boundary_edge == BoundaryEdge::E_X11 && subdomain_y == num_lateral_subdomains - 1 &&
1708 subdomain_r < num_radial_subdomains - 1 )
1709 {
1710 // Southern hemi to the top right + surface
1711 neighborhood_edge_[boundary_edge].emplace_back(
1712 SubdomainInfo(
1713 ( diamond_id - 4 ) % 5,
1714 num_lateral_subdomains - 1,
1715 num_lateral_subdomains - 1 - subdomain_x,
1716 subdomain_r + 1 ),
1719 -1 );
1720 }
1721 else if (
1722 boundary_edge == BoundaryEdge::E_1Y0 && subdomain_x == num_lateral_subdomains - 1 &&
1723 subdomain_r > 0 )
1724 {
1725 // Southern hemi to the top left + cmb
1726 neighborhood_edge_[boundary_edge].emplace_back(
1727 SubdomainInfo(
1728 diamond_id - 5,
1729 num_lateral_subdomains - 1 - subdomain_y,
1730 num_lateral_subdomains - 1,
1731 subdomain_r - 1 ),
1734 -1 );
1735 }
1736 else if (
1737 boundary_edge == BoundaryEdge::E_1Y1 && subdomain_x == num_lateral_subdomains - 1 &&
1738 subdomain_r < num_radial_subdomains - 1 )
1739 {
1740 // Southern hemi to the top left + surface
1741 neighborhood_edge_[boundary_edge].emplace_back(
1742 SubdomainInfo(
1743 diamond_id - 5,
1744 num_lateral_subdomains - 1 - subdomain_y,
1745 num_lateral_subdomains - 1,
1746 subdomain_r + 1 ),
1749 -1 );
1750 }
1751 }
1752 else
1753 {
1754 Kokkos::abort( "Invalid diamond ID" );
1755 }
1756
1757 // Now only same diamond cases
1758
1759 // Edges in radial direction
1760
1761 if ( boundary_edge == BoundaryEdge::E_00R && subdomain_x > 0 && subdomain_y > 0 )
1762 {
1763 neighborhood_edge_[boundary_edge].emplace_back(
1764 SubdomainInfo( diamond_id, subdomain_x - 1, subdomain_y - 1, subdomain_r ),
1767 -1 );
1768 }
1769 else if (
1770 boundary_edge == BoundaryEdge::E_10R && subdomain_x < num_lateral_subdomains - 1 && subdomain_y > 0 )
1771 {
1772 neighborhood_edge_[boundary_edge].emplace_back(
1773 SubdomainInfo( diamond_id, subdomain_x + 1, subdomain_y - 1, subdomain_r ),
1776 -1 );
1777 }
1778 else if (
1779 boundary_edge == BoundaryEdge::E_01R && subdomain_x > 0 && subdomain_y < num_lateral_subdomains - 1 )
1780 {
1781 neighborhood_edge_[boundary_edge].emplace_back(
1782 SubdomainInfo( diamond_id, subdomain_x - 1, subdomain_y + 1, subdomain_r ),
1785 -1 );
1786 }
1787 else if (
1788 boundary_edge == BoundaryEdge::E_11R && subdomain_x < num_lateral_subdomains - 1 &&
1789 subdomain_y < num_lateral_subdomains - 1 )
1790 {
1791 neighborhood_edge_[boundary_edge].emplace_back(
1792 SubdomainInfo( diamond_id, subdomain_x + 1, subdomain_y + 1, subdomain_r ),
1795 -1 );
1796 }
1797
1798 // Edges in Y direction
1799
1800 else if ( boundary_edge == BoundaryEdge::E_0Y0 && subdomain_x > 0 && subdomain_r > 0 )
1801 {
1802 neighborhood_edge_[boundary_edge].emplace_back(
1803 SubdomainInfo( diamond_id, subdomain_x - 1, subdomain_y, subdomain_r - 1 ),
1806 -1 );
1807 }
1808 else if (
1809 boundary_edge == BoundaryEdge::E_1Y0 && subdomain_x < num_lateral_subdomains - 1 && subdomain_r > 0 )
1810 {
1811 neighborhood_edge_[boundary_edge].emplace_back(
1812 SubdomainInfo( diamond_id, subdomain_x + 1, subdomain_y, subdomain_r - 1 ),
1815 -1 );
1816 }
1817 else if (
1818 boundary_edge == BoundaryEdge::E_0Y1 && subdomain_x > 0 && subdomain_r < num_radial_subdomains - 1 )
1819 {
1820 neighborhood_edge_[boundary_edge].emplace_back(
1821 SubdomainInfo( diamond_id, subdomain_x - 1, subdomain_y, subdomain_r + 1 ),
1824 -1 );
1825 }
1826 else if (
1827 boundary_edge == BoundaryEdge::E_1Y1 && subdomain_x < num_lateral_subdomains - 1 &&
1828 subdomain_r < num_radial_subdomains - 1 )
1829 {
1830 neighborhood_edge_[boundary_edge].emplace_back(
1831 SubdomainInfo( diamond_id, subdomain_x + 1, subdomain_y, subdomain_r + 1 ),
1834 -1 );
1835 }
1836
1837 // Radial (Y fixed)
1838
1839 else if ( boundary_edge == BoundaryEdge::E_X00 && subdomain_y > 0 && subdomain_r > 0 )
1840 {
1841 neighborhood_edge_[boundary_edge].emplace_back(
1842 SubdomainInfo( diamond_id, subdomain_x, subdomain_y - 1, subdomain_r - 1 ),
1845 -1 );
1846 }
1847 else if (
1848 boundary_edge == BoundaryEdge::E_X10 && subdomain_y < num_lateral_subdomains - 1 && subdomain_r > 0 )
1849 {
1850 neighborhood_edge_[boundary_edge].emplace_back(
1851 SubdomainInfo( diamond_id, subdomain_x, subdomain_y + 1, subdomain_r - 1 ),
1854 -1 );
1855 }
1856 else if (
1857 boundary_edge == BoundaryEdge::E_X01 && subdomain_y > 0 && subdomain_r < num_radial_subdomains - 1 )
1858 {
1859 neighborhood_edge_[boundary_edge].emplace_back(
1860 SubdomainInfo( diamond_id, subdomain_x, subdomain_y - 1, subdomain_r + 1 ),
1863 -1 );
1864 }
1865 else if (
1866 boundary_edge == BoundaryEdge::E_X11 && subdomain_y < num_lateral_subdomains - 1 &&
1867 subdomain_r < num_radial_subdomains - 1 )
1868 {
1869 neighborhood_edge_[boundary_edge].emplace_back(
1870 SubdomainInfo( diamond_id, subdomain_x, subdomain_y + 1, subdomain_r + 1 ),
1873 -1 );
1874 }
1875 }
1876 }
1877
1878 void setup_neighborhood_vertices( const DomainInfo& domain_info, const SubdomainInfo& subdomain_info )
1879 {
1880 const int diamond_id = subdomain_info.diamond_id();
1881 const int subdomain_x = subdomain_info.subdomain_x();
1882 const int subdomain_y = subdomain_info.subdomain_y();
1883 const int subdomain_r = subdomain_info.subdomain_r();
1884
1885 const int num_lateral_subdomains = domain_info.num_subdomains_per_diamond_side();
1886 const int num_radial_subdomains = domain_info.num_subdomains_in_radial_direction();
1887
1888 for ( const auto boundary_vertex : all_boundary_vertices )
1889 {
1890 // Across diamond boundaries
1891 if ( diamond_id >= 0 && diamond_id <= 4 )
1892 {
1893 {
1894 // north pole
1895 if ( boundary_vertex == BoundaryVertex::V_000 && subdomain_x == 0 && subdomain_y == 0 &&
1896 subdomain_r > 0 )
1897 {
1898 neighborhood_vertex_[boundary_vertex].emplace_back(
1899 SubdomainInfo( ( diamond_id + 2 ) % 5, 0, 0, subdomain_r - 1 ), BoundaryVertex::V_001, -1 );
1900 neighborhood_vertex_[boundary_vertex].emplace_back(
1901 SubdomainInfo( ( diamond_id + 3 ) % 5, 0, 0, subdomain_r - 1 ), BoundaryVertex::V_001, -1 );
1902 }
1903
1904 if ( boundary_vertex == BoundaryVertex::V_001 && subdomain_x == 0 && subdomain_y == 0 &&
1905 subdomain_r < num_radial_subdomains - 1 )
1906 {
1907 neighborhood_vertex_[boundary_vertex].emplace_back(
1908 SubdomainInfo( ( diamond_id + 2 ) % 5, 0, 0, subdomain_r + 1 ), BoundaryVertex::V_000, -1 );
1909 neighborhood_vertex_[boundary_vertex].emplace_back(
1910 SubdomainInfo( ( diamond_id + 3 ) % 5, 0, 0, subdomain_r + 1 ), BoundaryVertex::V_000, -1 );
1911 }
1912 }
1913
1914 {
1915 // Northern hemisphere to the right.
1916
1917 if ( boundary_vertex == BoundaryVertex::V_000 && subdomain_x == 0 && subdomain_y > 0 &&
1918 subdomain_r > 0 )
1919 {
1920 neighborhood_vertex_[boundary_vertex].emplace_back(
1921 SubdomainInfo( ( diamond_id + 1 ) % 5, subdomain_y - 1, 0, subdomain_r - 1 ),
1923 -1 );
1924 }
1925 else if (
1926 boundary_vertex == BoundaryVertex::V_001 && subdomain_x == 0 && subdomain_y > 0 &&
1927 subdomain_r < num_radial_subdomains - 1 )
1928 {
1929 neighborhood_vertex_[boundary_vertex].emplace_back(
1930 SubdomainInfo( ( diamond_id + 1 ) % 5, subdomain_y - 1, 0, subdomain_r + 1 ),
1932 -1 );
1933 }
1934 }
1935
1936 {
1937 // Northern hemisphere to the left.
1938
1939 if ( boundary_vertex == BoundaryVertex::V_000 && subdomain_x > 0 && subdomain_y == 0 &&
1940 subdomain_r > 0 )
1941 {
1942 neighborhood_vertex_[boundary_vertex].emplace_back(
1943 SubdomainInfo( ( diamond_id + 4 ) % 5, 0, subdomain_x - 1, subdomain_r - 1 ),
1945 -1 );
1946 }
1947 else if (
1948 boundary_vertex == BoundaryVertex::V_001 && subdomain_x > 0 && subdomain_y == 0 &&
1949 subdomain_r < num_radial_subdomains - 1 )
1950 {
1951 neighborhood_vertex_[boundary_vertex].emplace_back(
1952 SubdomainInfo( ( diamond_id + 4 ) % 5, 0, subdomain_x - 1, subdomain_r + 1 ),
1954 -1 );
1955 }
1956 }
1957
1958 {
1959 // Northern hemisphere to the right
1960
1961 if ( boundary_vertex == BoundaryVertex::V_010 && subdomain_x == 0 &&
1962 subdomain_y < num_lateral_subdomains - 1 && subdomain_r > 0 )
1963 {
1964 neighborhood_vertex_[boundary_vertex].emplace_back(
1965 SubdomainInfo( ( diamond_id + 1 ) % 5, subdomain_y + 1, 0, subdomain_r - 1 ),
1967 -1 );
1968 }
1969 else if (
1970 boundary_vertex == BoundaryVertex::V_011 && subdomain_x == 0 &&
1971 subdomain_y < num_lateral_subdomains - 1 && subdomain_r < num_radial_subdomains - 1 )
1972 {
1973 neighborhood_vertex_[boundary_vertex].emplace_back(
1974 SubdomainInfo( ( diamond_id + 1 ) % 5, subdomain_y + 1, 0, subdomain_r + 1 ),
1976 -1 );
1977 }
1978 }
1979
1980 {
1981 // Northern hemisphere to the right and southern
1982
1983 if ( boundary_vertex == BoundaryVertex::V_010 && subdomain_x > 0 &&
1984 subdomain_y == num_lateral_subdomains - 1 && subdomain_r > 0 )
1985 {
1986 neighborhood_vertex_[boundary_vertex].emplace_back(
1987 SubdomainInfo(
1988 diamond_id + 5,
1989 num_lateral_subdomains - 1,
1990 num_lateral_subdomains - subdomain_x,
1991 subdomain_r - 1 ),
1993 -1 );
1994 }
1995 else if (
1996 boundary_vertex == BoundaryVertex::V_011 && subdomain_x > 0 &&
1997 subdomain_y == num_lateral_subdomains - 1 && subdomain_r < num_radial_subdomains - 1 )
1998 {
1999 neighborhood_vertex_[boundary_vertex].emplace_back(
2000 SubdomainInfo(
2001 diamond_id + 5,
2002 num_lateral_subdomains - 1,
2003 num_lateral_subdomains - subdomain_x,
2004 subdomain_r + 1 ),
2006 -1 );
2007 }
2008 }
2009
2010 {
2011 // Northern hemisphere to the left
2012
2013 if ( boundary_vertex == BoundaryVertex::V_100 && subdomain_y == 0 &&
2014 subdomain_x < num_lateral_subdomains - 1 && subdomain_r > 0 )
2015 {
2016 neighborhood_vertex_[boundary_vertex].emplace_back(
2017 SubdomainInfo( ( diamond_id + 4 ) % 5, 0, subdomain_x + 1, subdomain_r - 1 ),
2019 -1 );
2020 }
2021 else if (
2022 boundary_vertex == BoundaryVertex::V_101 && subdomain_y == 0 &&
2023 subdomain_x < num_lateral_subdomains - 1 && subdomain_r < num_radial_subdomains - 1 )
2024 {
2025 neighborhood_vertex_[boundary_vertex].emplace_back(
2026 SubdomainInfo( ( diamond_id + 4 ) % 5, 0, subdomain_x + 1, subdomain_r + 1 ),
2028 -1 );
2029 }
2030 }
2031
2032 {
2033 // Northern hemisphere to the left and southern
2034
2035 if ( boundary_vertex == BoundaryVertex::V_100 && subdomain_y > 0 &&
2036 subdomain_x == num_lateral_subdomains - 1 && subdomain_r > 0 )
2037 {
2038 neighborhood_vertex_[boundary_vertex].emplace_back(
2039 SubdomainInfo(
2040 ( diamond_id + 4 ) % 5 + 5,
2041 num_lateral_subdomains - subdomain_y,
2042 num_lateral_subdomains - 1,
2043 subdomain_r - 1 ),
2045 -1 );
2046 }
2047 else if (
2048 boundary_vertex == BoundaryVertex::V_101 && subdomain_y > 0 &&
2049 subdomain_x == num_lateral_subdomains - 1 && subdomain_r < num_radial_subdomains - 1 )
2050 {
2051 neighborhood_vertex_[boundary_vertex].emplace_back(
2052 SubdomainInfo(
2053 ( diamond_id + 4 ) % 5 + 5,
2054 num_lateral_subdomains - subdomain_y,
2055 num_lateral_subdomains - 1,
2056 subdomain_r + 1 ),
2058 -1 );
2059 }
2060 }
2061
2062 {
2063 // Northern hemi to southern (right)
2064
2065 if ( boundary_vertex == BoundaryVertex::V_110 && subdomain_x < num_lateral_subdomains - 1 &&
2066 subdomain_y == num_lateral_subdomains - 1 && subdomain_r > 0 )
2067 {
2068 neighborhood_vertex_[boundary_vertex].emplace_back(
2069 SubdomainInfo(
2070 diamond_id + 5,
2071 num_lateral_subdomains - 1,
2072 num_lateral_subdomains - subdomain_x - 2,
2073 subdomain_r - 1 ),
2075 -1 );
2076 }
2077 else if (
2078 boundary_vertex == BoundaryVertex::V_111 && subdomain_x < num_lateral_subdomains - 1 &&
2079 subdomain_y == num_lateral_subdomains - 1 && subdomain_r < num_radial_subdomains - 1 )
2080 {
2081 neighborhood_vertex_[boundary_vertex].emplace_back(
2082 SubdomainInfo(
2083 diamond_id + 5,
2084 num_lateral_subdomains - 1,
2085 num_lateral_subdomains - subdomain_x - 2,
2086 subdomain_r + 1 ),
2088 -1 );
2089 }
2090 }
2091
2092 {
2093 // Northern hemi to southern (left)
2094
2095 if ( boundary_vertex == BoundaryVertex::V_110 && subdomain_y < num_lateral_subdomains - 1 &&
2096 subdomain_x == num_lateral_subdomains - 1 && subdomain_r > 0 )
2097 {
2098 neighborhood_vertex_[boundary_vertex].emplace_back(
2099 SubdomainInfo(
2100 ( diamond_id + 4 ) % 5 + 5,
2101 num_lateral_subdomains - subdomain_y - 2,
2102 num_lateral_subdomains - 1,
2103 subdomain_r - 1 ),
2105 -1 );
2106 }
2107 else if (
2108 boundary_vertex == BoundaryVertex::V_111 && subdomain_y < num_lateral_subdomains - 1 &&
2109 subdomain_x == num_lateral_subdomains - 1 && subdomain_r < num_radial_subdomains - 1 )
2110 {
2111 neighborhood_vertex_[boundary_vertex].emplace_back(
2112 SubdomainInfo(
2113 ( diamond_id + 4 ) % 5 + 5,
2114 num_lateral_subdomains - subdomain_y - 2,
2115 num_lateral_subdomains - 1,
2116 subdomain_r + 1 ),
2118 -1 );
2119 }
2120 }
2121 }
2122 else if ( diamond_id >= 5 && diamond_id <= 9 )
2123 {
2124 {
2125 // south pole
2126 if ( boundary_vertex == BoundaryVertex::V_000 && subdomain_x == 0 && subdomain_y == 0 &&
2127 subdomain_r > 0 )
2128 {
2129 neighborhood_vertex_[boundary_vertex].emplace_back(
2130 SubdomainInfo( ( diamond_id + 2 ) % 5 + 5, 0, 0, subdomain_r - 1 ),
2132 -1 );
2133 neighborhood_vertex_[boundary_vertex].emplace_back(
2134 SubdomainInfo( ( diamond_id + 3 ) % 5 + 5, 0, 0, subdomain_r - 1 ),
2136 -1 );
2137 }
2138
2139 if ( boundary_vertex == BoundaryVertex::V_001 && subdomain_x == 0 && subdomain_y == 0 &&
2140 subdomain_r < num_radial_subdomains - 1 )
2141 {
2142 neighborhood_vertex_[boundary_vertex].emplace_back(
2143 SubdomainInfo( ( diamond_id + 2 ) % 5 + 5, 0, 0, subdomain_r + 1 ),
2145 -1 );
2146 neighborhood_vertex_[boundary_vertex].emplace_back(
2147 SubdomainInfo( ( diamond_id + 3 ) % 5 + 5, 0, 0, subdomain_r + 1 ),
2149 -1 );
2150 }
2151 }
2152
2153 {
2154 // Southern hemisphere to the right.
2155
2156 if ( boundary_vertex == BoundaryVertex::V_000 && subdomain_x == 0 && subdomain_y > 0 &&
2157 subdomain_r > 0 )
2158 {
2159 neighborhood_vertex_[boundary_vertex].emplace_back(
2160 SubdomainInfo( ( diamond_id + 1 ) % 5 + 5, subdomain_y - 1, 0, subdomain_r - 1 ),
2162 -1 );
2163 }
2164 else if (
2165 boundary_vertex == BoundaryVertex::V_001 && subdomain_x == 0 && subdomain_y > 0 &&
2166 subdomain_r < num_radial_subdomains - 1 )
2167 {
2168 neighborhood_vertex_[boundary_vertex].emplace_back(
2169 SubdomainInfo( ( diamond_id + 1 ) % 5 + 5, subdomain_y - 1, 0, subdomain_r + 1 ),
2171 -1 );
2172 }
2173 }
2174
2175 {
2176 // Southern hemisphere to the left.
2177
2178 if ( boundary_vertex == BoundaryVertex::V_000 && subdomain_x > 0 && subdomain_y == 0 &&
2179 subdomain_r > 0 )
2180 {
2181 neighborhood_vertex_[boundary_vertex].emplace_back(
2182 SubdomainInfo( ( diamond_id + 4 ) % 5 + 5, 0, subdomain_x - 1, subdomain_r - 1 ),
2184 -1 );
2185 }
2186 else if (
2187 boundary_vertex == BoundaryVertex::V_001 && subdomain_x > 0 && subdomain_y == 0 &&
2188 subdomain_r < num_radial_subdomains - 1 )
2189 {
2190 neighborhood_vertex_[boundary_vertex].emplace_back(
2191 SubdomainInfo( ( diamond_id + 4 ) % 5 + 5, 0, subdomain_x - 1, subdomain_r + 1 ),
2193 -1 );
2194 }
2195 }
2196
2197 {
2198 // Southern hemisphere to the right
2199
2200 if ( boundary_vertex == BoundaryVertex::V_010 && subdomain_x == 0 &&
2201 subdomain_y < num_lateral_subdomains - 1 && subdomain_r > 0 )
2202 {
2203 neighborhood_vertex_[boundary_vertex].emplace_back(
2204 SubdomainInfo( ( diamond_id + 1 ) % 5 + 5, subdomain_y + 1, 0, subdomain_r - 1 ),
2206 -1 );
2207 }
2208 else if (
2209 boundary_vertex == BoundaryVertex::V_011 && subdomain_x == 0 &&
2210 subdomain_y < num_lateral_subdomains - 1 && subdomain_r < num_radial_subdomains - 1 )
2211 {
2212 neighborhood_vertex_[boundary_vertex].emplace_back(
2213 SubdomainInfo( ( diamond_id + 1 ) % 5 + 5, subdomain_y + 1, 0, subdomain_r + 1 ),
2215 -1 );
2216 }
2217 }
2218
2219 {
2220 // Southern hemisphere to the right and north
2221
2222 if ( boundary_vertex == BoundaryVertex::V_010 && subdomain_x > 0 &&
2223 subdomain_y == num_lateral_subdomains - 1 && subdomain_r > 0 )
2224 {
2225 neighborhood_vertex_[boundary_vertex].emplace_back(
2226 SubdomainInfo(
2227 ( diamond_id + 1 ) % 5,
2228 num_lateral_subdomains - 1,
2229 num_lateral_subdomains - subdomain_x,
2230 subdomain_r - 1 ),
2232 -1 );
2233 }
2234 else if (
2235 boundary_vertex == BoundaryVertex::V_011 && subdomain_x > 0 &&
2236 subdomain_y == num_lateral_subdomains - 1 && subdomain_r < num_radial_subdomains - 1 )
2237 {
2238 neighborhood_vertex_[boundary_vertex].emplace_back(
2239 SubdomainInfo(
2240 ( diamond_id + 1 ) % 5,
2241 num_lateral_subdomains - 1,
2242 num_lateral_subdomains - subdomain_x,
2243 subdomain_r + 1 ),
2245 -1 );
2246 }
2247 }
2248
2249 {
2250 // Southern hemisphere to the left
2251
2252 if ( boundary_vertex == BoundaryVertex::V_100 && subdomain_y == 0 &&
2253 subdomain_x < num_lateral_subdomains - 1 && subdomain_r > 0 )
2254 {
2255 neighborhood_vertex_[boundary_vertex].emplace_back(
2256 SubdomainInfo( ( diamond_id - 1 ) % 5 + 5, 0, subdomain_x + 1, subdomain_r - 1 ),
2258 -1 );
2259 }
2260 else if (
2261 boundary_vertex == BoundaryVertex::V_101 && subdomain_y == 0 &&
2262 subdomain_x < num_lateral_subdomains - 1 && subdomain_r < num_radial_subdomains - 1 )
2263 {
2264 neighborhood_vertex_[boundary_vertex].emplace_back(
2265 SubdomainInfo( ( diamond_id - 1 ) % 5 + 5, 0, subdomain_x + 1, subdomain_r + 1 ),
2267 -1 );
2268 }
2269 }
2270
2271 {
2272 // Southern hemisphere to the left and northern
2273
2274 if ( boundary_vertex == BoundaryVertex::V_100 && subdomain_y > 0 &&
2275 subdomain_x == num_lateral_subdomains - 1 && subdomain_r > 0 )
2276 {
2277 neighborhood_vertex_[boundary_vertex].emplace_back(
2278 SubdomainInfo(
2279 diamond_id - 5,
2280 num_lateral_subdomains - subdomain_y,
2281 num_lateral_subdomains - 1,
2282 subdomain_r - 1 ),
2284 -1 );
2285 }
2286 else if (
2287 boundary_vertex == BoundaryVertex::V_101 && subdomain_y > 0 &&
2288 subdomain_x == num_lateral_subdomains - 1 && subdomain_r < num_radial_subdomains - 1 )
2289 {
2290 neighborhood_vertex_[boundary_vertex].emplace_back(
2291 SubdomainInfo(
2292 diamond_id - 5,
2293 num_lateral_subdomains - subdomain_y,
2294 num_lateral_subdomains - 1,
2295 subdomain_r + 1 ),
2297 -1 );
2298 }
2299 }
2300
2301 {
2302 // Southern hemi to northern (right)
2303
2304 if ( boundary_vertex == BoundaryVertex::V_110 && subdomain_x < num_lateral_subdomains - 1 &&
2305 subdomain_y == num_lateral_subdomains - 1 && subdomain_r > 0 )
2306 {
2307 neighborhood_vertex_[boundary_vertex].emplace_back(
2308 SubdomainInfo(
2309 ( diamond_id - 4 ) % 5,
2310 num_lateral_subdomains - 1,
2311 num_lateral_subdomains - subdomain_x - 2,
2312 subdomain_r - 1 ),
2314 -1 );
2315 }
2316 else if (
2317 boundary_vertex == BoundaryVertex::V_111 && subdomain_x < num_lateral_subdomains - 1 &&
2318 subdomain_y == num_lateral_subdomains - 1 && subdomain_r < num_radial_subdomains - 1 )
2319 {
2320 neighborhood_vertex_[boundary_vertex].emplace_back(
2321 SubdomainInfo(
2322 ( diamond_id - 4 ) % 5,
2323 num_lateral_subdomains - 1,
2324 num_lateral_subdomains - subdomain_x - 2,
2325 subdomain_r + 1 ),
2327 -1 );
2328 }
2329 }
2330
2331 {
2332 // Southern hemi to northern (left)
2333 if ( boundary_vertex == BoundaryVertex::V_110 && subdomain_y < num_lateral_subdomains - 1 &&
2334 subdomain_x == num_lateral_subdomains - 1 && subdomain_r > 0 )
2335 {
2336 neighborhood_vertex_[boundary_vertex].emplace_back(
2337 SubdomainInfo(
2338 diamond_id - 5,
2339 num_lateral_subdomains - subdomain_y - 2,
2340 num_lateral_subdomains - 1,
2341 subdomain_r - 1 ),
2343 -1 );
2344 }
2345 else if (
2346 boundary_vertex == BoundaryVertex::V_111 && subdomain_y < num_lateral_subdomains - 1 &&
2347 subdomain_x == num_lateral_subdomains - 1 && subdomain_r < num_radial_subdomains - 1 )
2348 {
2349 neighborhood_vertex_[boundary_vertex].emplace_back(
2350 SubdomainInfo(
2351 diamond_id - 5,
2352 num_lateral_subdomains - subdomain_y - 2,
2353 num_lateral_subdomains - 1,
2354 subdomain_r + 1 ),
2356 -1 );
2357 }
2358 }
2359 }
2360 else
2361 {
2362 Kokkos::abort( "Invalid diamond ID." );
2363 }
2364
2365 // Same diamond:
2366
2367 if ( boundary_vertex == BoundaryVertex::V_000 && subdomain_x > 0 && subdomain_y > 0 && subdomain_r > 0 )
2368 {
2369 neighborhood_vertex_[boundary_vertex].emplace_back(
2370 SubdomainInfo( diamond_id, subdomain_x - 1, subdomain_y - 1, subdomain_r - 1 ),
2372 -1 );
2373 }
2374 else if (
2375 boundary_vertex == BoundaryVertex::V_100 && subdomain_x < num_lateral_subdomains - 1 &&
2376 subdomain_y > 0 && subdomain_r > 0 )
2377 {
2378 neighborhood_vertex_[boundary_vertex].emplace_back(
2379 SubdomainInfo( diamond_id, subdomain_x + 1, subdomain_y - 1, subdomain_r - 1 ),
2381 -1 );
2382 }
2383 else if (
2384 boundary_vertex == BoundaryVertex::V_010 && subdomain_x > 0 &&
2385 subdomain_y < num_lateral_subdomains - 1 && subdomain_r > 0 )
2386 {
2387 neighborhood_vertex_[boundary_vertex].emplace_back(
2388 SubdomainInfo( diamond_id, subdomain_x - 1, subdomain_y + 1, subdomain_r - 1 ),
2390 -1 );
2391 }
2392 else if (
2393 boundary_vertex == BoundaryVertex::V_001 && subdomain_x > 0 && subdomain_y > 0 &&
2394 subdomain_r < num_radial_subdomains - 1 )
2395 {
2396 neighborhood_vertex_[boundary_vertex].emplace_back(
2397 SubdomainInfo( diamond_id, subdomain_x - 1, subdomain_y - 1, subdomain_r + 1 ),
2399 -1 );
2400 }
2401 else if (
2402 boundary_vertex == BoundaryVertex::V_110 && subdomain_x < num_lateral_subdomains - 1 &&
2403 subdomain_y < num_lateral_subdomains - 1 && subdomain_r > 0 )
2404 {
2405 neighborhood_vertex_[boundary_vertex].emplace_back(
2406 SubdomainInfo( diamond_id, subdomain_x + 1, subdomain_y + 1, subdomain_r - 1 ),
2408 -1 );
2409 }
2410 else if (
2411 boundary_vertex == BoundaryVertex::V_101 && subdomain_x < num_lateral_subdomains - 1 &&
2412 subdomain_y > 0 && subdomain_r < num_radial_subdomains - 1 )
2413 {
2414 neighborhood_vertex_[boundary_vertex].emplace_back(
2415 SubdomainInfo( diamond_id, subdomain_x + 1, subdomain_y - 1, subdomain_r + 1 ),
2417 -1 );
2418 }
2419 else if (
2420 boundary_vertex == BoundaryVertex::V_011 && subdomain_x > 0 &&
2421 subdomain_y < num_lateral_subdomains - 1 && subdomain_r < num_radial_subdomains - 1 )
2422 {
2423 neighborhood_vertex_[boundary_vertex].emplace_back(
2424 SubdomainInfo( diamond_id, subdomain_x - 1, subdomain_y + 1, subdomain_r + 1 ),
2426 -1 );
2427 }
2428 else if (
2429 boundary_vertex == BoundaryVertex::V_111 && subdomain_x < num_lateral_subdomains - 1 &&
2430 subdomain_y < num_lateral_subdomains - 1 && subdomain_r < num_radial_subdomains - 1 )
2431 {
2432 neighborhood_vertex_[boundary_vertex].emplace_back(
2433 SubdomainInfo( diamond_id, subdomain_x + 1, subdomain_y + 1, subdomain_r + 1 ),
2435 -1 );
2436 }
2437 }
2438 }
2439
2440 void setup_neighborhood_ranks(
2441 const DomainInfo& domain_info,
2442 const SubdomainToRankDistributionFunction& subdomain_to_rank )
2443 {
2444 for ( auto& neighbors : neighborhood_vertex_ | std::views::values )
2445 {
2446 for ( auto& [neighbor_subdomain_info, neighbor_boundary_vertex, neighbor_rank] : neighbors )
2447 {
2448 neighbor_rank = subdomain_to_rank(
2449 neighbor_subdomain_info,
2450 domain_info.num_subdomains_per_diamond_side(),
2451 domain_info.num_subdomains_in_radial_direction() );
2452 }
2453 }
2454
2455 for ( auto& neighbors : neighborhood_edge_ | std::views::values )
2456 {
2457 for ( auto& [neighbor_subdomain_info, neighbor_boundary_edge, _, neighbor_rank] : neighbors )
2458 {
2459 neighbor_rank = subdomain_to_rank(
2460 neighbor_subdomain_info,
2461 domain_info.num_subdomains_per_diamond_side(),
2462 domain_info.num_subdomains_in_radial_direction() );
2463 }
2464 }
2465
2466 for ( auto& [neighbor_subdomain_info, neighbor_boundary_face, _, neighbor_rank] :
2467 neighborhood_face_ | std::views::values )
2468 {
2469 neighbor_rank = subdomain_to_rank(
2470 neighbor_subdomain_info,
2471 domain_info.num_subdomains_per_diamond_side(),
2472 domain_info.num_subdomains_in_radial_direction() );
2473 }
2474 }
2475
2476 void setup_neighborhood(
2477 const DomainInfo& domain_info,
2478 const SubdomainInfo& subdomain_info,
2479 const SubdomainToRankDistributionFunction& subdomain_to_rank )
2480 {
2481 setup_neighborhood_faces( domain_info, subdomain_info );
2482 setup_neighborhood_edges( domain_info, subdomain_info );
2483 setup_neighborhood_vertices( domain_info, subdomain_info );
2484 setup_neighborhood_ranks( domain_info, subdomain_to_rank );
2485 }
2486
2487 std::map< BoundaryVertex, std::vector< NeighborSubdomainTupleVertex > > neighborhood_vertex_;
2488 std::map< BoundaryEdge, std::vector< NeighborSubdomainTupleEdge > > neighborhood_edge_;
2489 std::map< BoundaryFace, NeighborSubdomainTupleFace > neighborhood_face_;
2490};
2491
2492/// @brief Parallel data structure organizing the thick spherical shell metadata for distributed (MPI parallel)
2493/// simulations.
2494///
2495/// This is essentially a wrapper for the \ref DomainInfo and the neighborhood information (\ref SubdomainNeighborhood)
2496/// for all process-local subdomains.
2498{
2499 public:
2501
2503
2504 /// @brief Creates a \ref DistributedDomain with a single subdomain per diamond and initializes all the subdomain
2505 /// neighborhoods.
2507 const int lateral_diamond_refinement_level,
2508 const int radial_diamond_refinement_level,
2509 const real_t r_min,
2510 const real_t r_max,
2512 {
2513 return create_uniform(
2514 lateral_diamond_refinement_level,
2515 uniform_shell_radii( r_min, r_max, ( 1 << radial_diamond_refinement_level ) + 1 ),
2516 0,
2517 0,
2518 subdomain_to_rank );
2519 }
2520
2521 /// @brief Creates a \ref DistributedDomain with a single subdomain per diamond and initializes all the subdomain
2522 /// neighborhoods.
2524 const int lateral_diamond_refinement_level,
2525 const std::vector< double >& radii,
2527 {
2528 return create_uniform( lateral_diamond_refinement_level, radii, 0, 0, subdomain_to_rank );
2529 }
2530
2531 /// @brief Creates a \ref DistributedDomain with a single subdomain per diamond and initializes all the subdomain
2532 /// neighborhoods.
2534 const int lateral_diamond_refinement_level,
2535 const int radial_diamond_refinement_level,
2536 const real_t r_min,
2537 const real_t r_max,
2538 const int lateral_subdomain_refinement_level,
2539 const int radial_subdomain_refinement_level,
2541 {
2542 return create_uniform(
2543 lateral_diamond_refinement_level,
2544 uniform_shell_radii( r_min, r_max, ( 1 << radial_diamond_refinement_level ) + 1 ),
2545 lateral_subdomain_refinement_level,
2546 radial_subdomain_refinement_level,
2547 subdomain_to_rank );
2548 }
2549
2550 /// @brief Creates a \ref DistributedDomain with a single subdomain per diamond and initializes all the subdomain
2551 /// neighborhoods.
2553 const int lateral_diamond_refinement_level,
2554 const std::vector< double >& radii,
2555 const int lateral_subdomain_refinement_level,
2556 const int radial_subdomain_refinement_level,
2558 {
2559 DistributedDomain domain;
2560 domain.domain_info_ = DomainInfo(
2561 lateral_diamond_refinement_level,
2562 radii,
2563 ( 1 << lateral_subdomain_refinement_level ),
2564 ( 1 << radial_subdomain_refinement_level ) );
2565 int idx = 0;
2566 for ( const auto& subdomain : domain.domain_info_.local_subdomains( subdomain_to_rank ) )
2567 {
2568 domain.subdomains_[subdomain] = {
2569 idx, SubdomainNeighborhood( domain.domain_info_, subdomain, subdomain_to_rank ) };
2570 domain.local_subdomain_index_to_subdomain_info_[idx] = subdomain;
2571 idx++;
2572 }
2573 return domain;
2574 }
2575
2576 /// @brief Returns a const reference
2577 [[nodiscard]] const DomainInfo& domain_info() const { return domain_info_; }
2578
2579 [[nodiscard]] const std::map< SubdomainInfo, std::tuple< LocalSubdomainIdx, SubdomainNeighborhood > >&
2581 {
2582 return subdomains_;
2583 }
2584
2585 [[nodiscard]] const SubdomainInfo& subdomain_info_from_local_idx( const LocalSubdomainIdx subdomain_idx ) const
2586 {
2587 return local_subdomain_index_to_subdomain_info_.at( subdomain_idx );
2588 }
2589
2590 private:
2591 DomainInfo domain_info_;
2592 std::map< SubdomainInfo, std::tuple< LocalSubdomainIdx, SubdomainNeighborhood > > subdomains_;
2593 std::map< LocalSubdomainIdx, SubdomainInfo > local_subdomain_index_to_subdomain_info_;
2594};
2595
2597{
2599 int min;
2600 int max;
2601 double avg;
2602};
2603
2605{
2606 const auto num_local_subdomains = static_cast< int >( domain.subdomains().size() );
2607 int total = num_local_subdomains;
2608 int min = num_local_subdomains;
2609 int max = num_local_subdomains;
2610
2611 MPI_Reduce( &num_local_subdomains, &total, 1, MPI_INT, MPI_SUM, 0, MPI_COMM_WORLD );
2612 MPI_Reduce( &num_local_subdomains, &min, 1, MPI_INT, MPI_MIN, 0, MPI_COMM_WORLD );
2613 MPI_Reduce( &num_local_subdomains, &max, 1, MPI_INT, MPI_MAX, 0, MPI_COMM_WORLD );
2614
2615 const SubdomainDistribution result{
2616 .total = total, .min = min, .max = max, .avg = static_cast< double >( total ) / mpi::num_processes() };
2617 return result;
2618}
2619
2620template < typename ValueType >
2622 allocate_scalar_grid( const std::string label, const DistributedDomain& distributed_domain )
2623{
2625 label,
2626 distributed_domain.subdomains().size(),
2629 distributed_domain.domain_info().subdomain_num_nodes_radially() );
2630}
2631
2632template < typename ValueType >
2634 allocate_scalar_grid( const std::string label, const DistributedDomain& distributed_domain, const int level )
2635{
2637 label,
2638 distributed_domain.subdomains().size(),
2639 distributed_domain.domain_info().subdomain_num_nodes_per_side_laterally( level ),
2640 distributed_domain.domain_info().subdomain_num_nodes_per_side_laterally( level ),
2641 distributed_domain.domain_info().subdomain_num_nodes_radially( level ) );
2642}
2643
2644inline Kokkos::MDRangePolicy< Kokkos::Rank< 4 > >
2646{
2647 return Kokkos::MDRangePolicy< Kokkos::Rank< 4 > >(
2648 { 0, 0, 0, 0 },
2649 { static_cast< long long >( distributed_domain.subdomains().size() ),
2652 distributed_domain.domain_info().subdomain_num_nodes_radially() } );
2653}
2654
2655// loop only lateral dimensions of each subdomain. Used in the precomputation of lateral parts of the
2656// Jacobian (-> Oliver)
2657inline Kokkos::MDRangePolicy< Kokkos::Rank< 3 > >
2659{
2660 return Kokkos::MDRangePolicy< Kokkos::Rank< 3 > >(
2661 { 0, 0, 0 },
2662 { static_cast< long long >( distributed_domain.subdomains().size() ),
2663 distributed_domain.domain_info().subdomain_num_nodes_per_side_laterally() - 1,
2664 distributed_domain.domain_info().subdomain_num_nodes_per_side_laterally() - 1 } );
2665}
2666
2667inline Kokkos::MDRangePolicy< Kokkos::Rank< 4 > >
2669{
2670 return Kokkos::MDRangePolicy< Kokkos::Rank< 4 > >(
2671 { 0, 0, 0, 0 },
2672 { static_cast< long long >( distributed_domain.subdomains().size() ),
2673 distributed_domain.domain_info().subdomain_num_nodes_per_side_laterally() - 1,
2674 distributed_domain.domain_info().subdomain_num_nodes_per_side_laterally() - 1,
2675 distributed_domain.domain_info().subdomain_num_nodes_radially() - 1 } );
2676}
2677
2678// linearized Range instaed of MDRange to loop lateral and radial dimension,
2679// potentially yields a performance advantage.
2680inline Kokkos::RangePolicy<>
2682{
2683 return Kokkos::RangePolicy<>(
2684 0,
2685 static_cast< long long >( distributed_domain.subdomains().size() ) *
2686 ( distributed_domain.domain_info().subdomain_num_nodes_per_side_laterally() - 1 ) *
2687 ( distributed_domain.domain_info().subdomain_num_nodes_per_side_laterally() - 1 ) *
2688 ( distributed_domain.domain_info().subdomain_num_nodes_radially() - 1 ) );
2689}
2690
2691/// @brief Returns an initialized grid with the coordinates of all subdomains' nodes projected to the unit sphere.
2692///
2693/// The layout is
2694///
2695/// grid( local_subdomain_id, x_idx, y_idx, node_coord )
2696///
2697/// where node_coord is in {0, 1, 2} and refers to the cartesian coordinate of the point.
2698template < std::floating_point T >
2700{
2701 Grid3DDataVec< T, 3 > subdomain_coords(
2702 "subdomain_unit_sphere_coords",
2703 domain.subdomains().size(),
2706
2707 typename Grid3DDataVec< T, 3 >::HostMirror subdomain_coords_host = Kokkos::create_mirror_view( subdomain_coords );
2708
2709 for ( const auto& [subdomain_info, data] : domain.subdomains() )
2710 {
2711 const auto& [subdomain_idx, neighborhood] = data;
2712
2713 unit_sphere_single_shell_subdomain_coords< T >(
2714 subdomain_coords_host,
2715 subdomain_idx,
2716 subdomain_info.diamond_id(),
2719 subdomain_info.subdomain_x(),
2720 subdomain_info.subdomain_y() );
2721 }
2722
2723 Kokkos::deep_copy( subdomain_coords, subdomain_coords_host );
2724 return subdomain_coords;
2725}
2726
2727/// @brief Returns an initialized grid with the radii of all subdomain nodes.
2728///
2729/// The layout is
2730///
2731/// grid( local_subdomain_id, r_idx ) = radius
2732///
2733template < std::floating_point T >
2735{
2736 const int shells_per_subdomain = domain.domain_info().subdomain_num_nodes_radially();
2737 const int layers_per_subdomain = shells_per_subdomain - 1;
2738
2739 Grid2DDataScalar< T > radii_device( "subdomain_shell_radii", domain.subdomains().size(), shells_per_subdomain );
2740 typename Grid2DDataScalar< T >::HostMirror radii_host = Kokkos::create_mirror_view( radii_device );
2741
2742 for ( const auto& [subdomain_info, data] : domain.subdomains() )
2743 {
2744 const auto& [subdomain_idx, neighborhood] = data;
2745
2746 const int subdomain_innermost_node_idx = subdomain_info.subdomain_r() * layers_per_subdomain;
2747 const int subdomain_outermost_node_idx = subdomain_innermost_node_idx + layers_per_subdomain;
2748
2749 int j = 0;
2750 for ( int node_idx = subdomain_innermost_node_idx; node_idx <= subdomain_outermost_node_idx; node_idx++ )
2751 {
2752 radii_host( subdomain_idx, j ) = domain.domain_info().radii()[node_idx];
2753 j++;
2754 }
2755 }
2756
2757 Kokkos::deep_copy( radii_device, radii_host );
2758 return radii_device;
2759}
2760
2761/// @brief Returns an initialized grid with the shell index of all subdomain nodes.
2762///
2763/// The layout is
2764///
2765/// grid( local_subdomain_id, r_idx ) = global_shell_idx
2766///
2768{
2769 const int shells_per_subdomain = domain.domain_info().subdomain_num_nodes_radially();
2770 const int layers_per_subdomain = shells_per_subdomain - 1;
2771
2772 Grid2DDataScalar< int > shell_idx_device( "subdomain_shell_idx", domain.subdomains().size(), shells_per_subdomain );
2773 Grid2DDataScalar< int >::HostMirror shell_idx_host = Kokkos::create_mirror_view( shell_idx_device );
2774
2775 for ( const auto& [subdomain_info, data] : domain.subdomains() )
2776 {
2777 const auto& [subdomain_idx, neighborhood] = data;
2778 const int subdomain_innermost_node_idx = subdomain_info.subdomain_r() * layers_per_subdomain;
2779 for ( int j = 0; j < shells_per_subdomain; j++ )
2780 {
2781 shell_idx_host( subdomain_idx, j ) = subdomain_innermost_node_idx + j;
2782 }
2783 }
2784 Kokkos::deep_copy( shell_idx_device, shell_idx_host );
2785 return shell_idx_device;
2786}
2787
2788template < typename CoordsShellType, typename CoordsRadiiType >
2790 const int subdomain,
2791 const int x,
2792 const int y,
2793 const int r,
2794 const CoordsShellType& coords_shell,
2795 const CoordsRadiiType& coords_radii )
2796{
2797 using T = CoordsShellType::value_type;
2798 static_assert( std::is_same_v< T, typename CoordsRadiiType::value_type > );
2799
2800 static_assert(
2801 std::is_same_v< CoordsShellType, Grid3DDataVec< T, 3 > > ||
2802 std::is_same_v< CoordsShellType, typename Grid3DDataVec< T, 3 >::HostMirror > );
2803
2804 static_assert(
2805 std::is_same_v< CoordsRadiiType, Grid2DDataScalar< T > > ||
2806 std::is_same_v< CoordsRadiiType, typename Grid2DDataScalar< T >::HostMirror > );
2807
2809 coords( 0 ) = coords_shell( subdomain, x, y, 0 );
2810 coords( 1 ) = coords_shell( subdomain, x, y, 1 );
2811 coords( 2 ) = coords_shell( subdomain, x, y, 2 );
2812 return coords * coords_radii( subdomain, r );
2813}
2814
2815template < typename CoordsShellType, typename CoordsRadiiType >
2817 const dense::Vec< int, 4 > subdomain_x_y_r,
2818 const CoordsShellType& coords_shell,
2819 const CoordsRadiiType& coords_radii )
2820{
2821 using T = CoordsShellType::value_type;
2822 static_assert( std::is_same_v< T, typename CoordsRadiiType::value_type > );
2823
2824 static_assert(
2825 std::is_same_v< CoordsShellType, Grid3DDataVec< T, 3 > > ||
2826 std::is_same_v< CoordsShellType, typename Grid3DDataVec< T, 3 >::HostMirror > );
2827
2828 static_assert(
2829 std::is_same_v< CoordsRadiiType, Grid2DDataScalar< T > > ||
2830 std::is_same_v< CoordsRadiiType, typename Grid2DDataScalar< T >::HostMirror > );
2831
2832 return coords(
2833 subdomain_x_y_r( 0 ),
2834 subdomain_x_y_r( 1 ),
2835 subdomain_x_y_r( 2 ),
2836 subdomain_x_y_r( 3 ),
2837 coords_shell,
2838 coords_radii );
2839}
2840
2841} // namespace terra::grid::shell
Parallel data structure organizing the thick spherical shell metadata for distributed (MPI parallel) ...
Definition spherical_shell.hpp:2498
const std::map< SubdomainInfo, std::tuple< LocalSubdomainIdx, SubdomainNeighborhood > > & subdomains() const
Definition spherical_shell.hpp:2580
const SubdomainInfo & subdomain_info_from_local_idx(const LocalSubdomainIdx subdomain_idx) const
Definition spherical_shell.hpp:2585
int LocalSubdomainIdx
Definition spherical_shell.hpp:2502
static DistributedDomain create_uniform_single_subdomain_per_diamond(const int lateral_diamond_refinement_level, const std::vector< double > &radii, const SubdomainToRankDistributionFunction &subdomain_to_rank=subdomain_to_rank_iterate_diamond_subdomains)
Creates a DistributedDomain with a single subdomain per diamond and initializes all the subdomain nei...
Definition spherical_shell.hpp:2523
static DistributedDomain create_uniform(const int lateral_diamond_refinement_level, const std::vector< double > &radii, const int lateral_subdomain_refinement_level, const int radial_subdomain_refinement_level, const SubdomainToRankDistributionFunction &subdomain_to_rank=subdomain_to_rank_iterate_diamond_subdomains)
Creates a DistributedDomain with a single subdomain per diamond and initializes all the subdomain nei...
Definition spherical_shell.hpp:2552
const DomainInfo & domain_info() const
Returns a const reference.
Definition spherical_shell.hpp:2577
static DistributedDomain create_uniform_single_subdomain_per_diamond(const int lateral_diamond_refinement_level, const int radial_diamond_refinement_level, const real_t r_min, const real_t r_max, const SubdomainToRankDistributionFunction &subdomain_to_rank=subdomain_to_rank_iterate_diamond_subdomains)
Creates a DistributedDomain with a single subdomain per diamond and initializes all the subdomain nei...
Definition spherical_shell.hpp:2506
static DistributedDomain create_uniform(const int lateral_diamond_refinement_level, const int radial_diamond_refinement_level, const real_t r_min, const real_t r_max, const int lateral_subdomain_refinement_level, const int radial_subdomain_refinement_level, const SubdomainToRankDistributionFunction &subdomain_to_rank=subdomain_to_rank_iterate_diamond_subdomains)
Creates a DistributedDomain with a single subdomain per diamond and initializes all the subdomain nei...
Definition spherical_shell.hpp:2533
Information about the thick spherical shell mesh.
Definition spherical_shell.hpp:780
int subdomain_num_nodes_radially(const int level) const
Number of nodes in the radial direction of a subdomain on the passed level.
Definition spherical_shell.hpp:890
const std::vector< double > & radii() const
Definition spherical_shell.hpp:845
int diamond_lateral_refinement_level() const
Definition spherical_shell.hpp:843
int subdomain_num_nodes_radially() const
Equivalent to calling subdomain_num_nodes_radially( subdomain_refinement_level() )
Definition spherical_shell.hpp:861
int subdomain_num_nodes_per_side_laterally() const
Equivalent to calling subdomain_num_nodes_per_side_laterally( subdomain_refinement_level() )
Definition spherical_shell.hpp:852
int num_subdomains_per_diamond_side() const
Definition spherical_shell.hpp:847
std::vector< SubdomainInfo > local_subdomains(const SubdomainToRankDistributionFunction &subdomain_to_rank) const
Definition spherical_shell.hpp:906
int subdomain_num_nodes_per_side_laterally(const int level) const
Number of nodes in the lateral direction of a subdomain on the passed level.
Definition spherical_shell.hpp:871
int subdomain_max_refinement_level() const
The "maximum refinement level" of the subdomains.
Definition spherical_shell.hpp:833
DomainInfo(int diamond_lateral_refinement_level, const std::vector< double > &radii)
Constructs a thick spherical shell with one subdomain per diamond (10 subdomains total) and shells at...
Definition spherical_shell.hpp:794
int num_subdomains_in_radial_direction() const
Definition spherical_shell.hpp:849
DomainInfo(int diamond_lateral_refinement_level, const std::vector< double > &radii, int num_subdomains_in_lateral_direction, int num_subdomains_in_radial_direction)
Constructs a thick spherical shell with shells at specific radii.
Definition spherical_shell.hpp:809
(Sortable) Globally unique identifier for a single subdomain of a diamond.
Definition spherical_shell.hpp:595
int diamond_id() const
Diamond that subdomain is part of.
Definition spherical_shell.hpp:629
bool operator==(const SubdomainInfo &other) const
Definition spherical_shell.hpp:646
SubdomainInfo()
Creates invalid ID.
Definition spherical_shell.hpp:598
int64_t global_id() const
Scrambles the four indices (diamond ID, x, y, r) into a single integer.
Definition spherical_shell.hpp:663
SubdomainInfo(int diamond_id, int subdomain_x, int subdomain_y, int subdomain_r)
Creates unique subdomain ID.
Definition spherical_shell.hpp:606
bool operator<(const SubdomainInfo &other) const
Definition spherical_shell.hpp:640
int subdomain_r() const
Subdomain index in the radial direction (local to the diamond).
Definition spherical_shell.hpp:638
int subdomain_x() const
Subdomain index in lateral x-direction (local to the diamond).
Definition spherical_shell.hpp:632
int subdomain_y() const
Subdomain index in lateral y-direction (local to the diamond).
Definition spherical_shell.hpp:635
SubdomainInfo(const int64_t global_id)
Read from encoded 64-bit integer.
Definition spherical_shell.hpp:616
Neighborhood information of a single subdomain.
Definition spherical_shell.hpp:1031
SubdomainNeighborhood(const DomainInfo &domain_info, const SubdomainInfo &subdomain_info, const SubdomainToRankDistributionFunction &subdomain_to_rank)
Definition spherical_shell.hpp:1042
const std::map< BoundaryFace, NeighborSubdomainTupleFace > & neighborhood_face() const
Definition spherical_shell.hpp:1060
std::tuple< SubdomainInfo, BoundaryEdge, UnpackOrderingEdge, mpi::MPIRank > NeighborSubdomainTupleEdge
Definition spherical_shell.hpp:1037
std::tuple< SubdomainInfo, BoundaryFace, UnpackOrderingFace, mpi::MPIRank > NeighborSubdomainTupleFace
Definition spherical_shell.hpp:1038
const std::map< BoundaryEdge, std::vector< NeighborSubdomainTupleEdge > > & neighborhood_edge() const
Definition spherical_shell.hpp:1055
std::tuple< BoundaryDirection, BoundaryDirection > UnpackOrderingFace
Definition spherical_shell.hpp:1034
std::tuple< SubdomainInfo, BoundaryVertex, mpi::MPIRank > NeighborSubdomainTupleVertex
Definition spherical_shell.hpp:1036
const std::map< BoundaryVertex, std::vector< NeighborSubdomainTupleVertex > > & neighborhood_vertex() const
Definition spherical_shell.hpp:1050
Definition shell/bit_masks.hpp:8
T min_radial_h(const std::vector< T > &shell_radii)
Computes the min absolute distance of two entries in the passed vector of shell radii.
Definition spherical_shell.hpp:154
T max_radial_h(const std::vector< T > &shell_radii)
Computes the max absolute distance of two entries in the passed vector of shell radii.
Definition spherical_shell.hpp:175
Grid4DDataScalar< ValueType > allocate_scalar_grid(const std::string label, const DistributedDomain &distributed_domain)
Definition spherical_shell.hpp:2622
Grid3DDataVec< T, 3 > subdomain_unit_sphere_single_shell_coords(const DistributedDomain &domain)
Returns an initialized grid with the coordinates of all subdomains' nodes projected to the unit spher...
Definition spherical_shell.hpp:2699
Grid2DDataScalar< T > subdomain_shell_radii(const DistributedDomain &domain)
Returns an initialized grid with the radii of all subdomain nodes.
Definition spherical_shell.hpp:2734
dense::Vec< typename CoordsShellType::value_type, 3 > coords(const int subdomain, const int x, const int y, const int r, const CoordsShellType &coords_shell, const CoordsRadiiType &coords_radii)
Definition spherical_shell.hpp:2789
dense::Vec< T, 3 > compute_node_recursive(int i, int j, const BaseCorners< T > &corners, MemoizationCache< T > &cache)
Computes the coordinates for a specific node (i, j) in the final refined grid. Uses recursion and mem...
Definition spherical_shell.hpp:231
std::function< T(T) > make_tanh_boundary_cluster(T k)
Map to be used in mapped_shell_radii.
Definition spherical_shell.hpp:69
std::ostream & operator<<(std::ostream &os, const SubdomainInfo &si)
Definition spherical_shell.hpp:693
std::vector< T > uniform_shell_radii(T r_min, T r_max, int num_shells)
Computes the radial shell radii for a uniform grid.
Definition spherical_shell.hpp:28
mpi::MPIRank subdomain_to_rank_all_root(const SubdomainInfo &subdomain_info, const int num_subdomains_per_diamond_laterally, const int num_subdomains_per_diamond_radially)
Assigns all subdomains to root (rank 0).
Definition spherical_shell.hpp:703
std::vector< T > mapped_shell_radii(T r_min, T r_max, int num_shells, const std::function< T(T) > &map)
Computes the radial shell radii for a non-uniformly distributed grid.
Definition spherical_shell.hpp:121
SubdomainDistribution subdomain_distribution(const DistributedDomain &domain)
Definition spherical_shell.hpp:2604
Grid2DDataScalar< int > subdomain_shell_idx(const DistributedDomain &domain)
Returns an initialized grid with the shell index of all subdomain nodes.
Definition spherical_shell.hpp:2767
Kokkos::MDRangePolicy< Kokkos::Rank< 4 > > local_domain_md_range_policy_cells(const DistributedDomain &distributed_domain)
Definition spherical_shell.hpp:2668
Kokkos::MDRangePolicy< Kokkos::Rank< 3 > > local_domain_md_range_policy_cells_lateral(const DistributedDomain &distributed_domain)
Definition spherical_shell.hpp:2658
void unit_sphere_single_shell_subdomain_coords(const typename Grid3DDataVec< T, 3 >::HostMirror &subdomain_coords_host, int subdomain_idx, int diamond_id, int ntan, int i_start_incl, int i_end_incl, int j_start_incl, int j_end_incl)
Definition spherical_shell.hpp:420
Kokkos::MDRangePolicy< Kokkos::Rank< 4 > > local_domain_md_range_policy_nodes(const DistributedDomain &distributed_domain)
Definition spherical_shell.hpp:2645
mpi::MPIRank subdomain_to_rank_iterate_diamond_subdomains(const SubdomainInfo &subdomain_info, const int num_subdomains_per_diamond_laterally, const int num_subdomains_per_diamond_radially)
Distributes subdomains to ranks as evenly as possible.
Definition spherical_shell.hpp:717
std::map< std::pair< int, int >, dense::Vec< T, 3 > > MemoizationCache
Definition spherical_shell.hpp:219
Kokkos::RangePolicy local_domain_md_range_policy_cells_linearized(const DistributedDomain &distributed_domain)
Definition spherical_shell.hpp:2681
std::function< mpi::MPIRank(const SubdomainInfo &, const int, const int) > SubdomainToRankDistributionFunction
Definition spherical_shell.hpp:700
void compute_subdomain(const typename Grid3DDataVec< T, 3 >::HostMirror &subdomain_coords_host, int subdomain_idx, const BaseCorners< T > &corners, int i_start_incl, int i_end_incl, int j_start_incl, int j_end_incl)
Generates coordinates for a rectangular subdomain of the refined spherical grid.
Definition spherical_shell.hpp:361
Kokkos::View< ScalarType ***[VecDim], Layout > Grid3DDataVec
Definition grid_types.hpp:40
constexpr std::array all_boundary_faces
Definition grid_types.hpp:266
BoundaryDirection
Enum for the iteration direction at a boundary.
Definition grid_types.hpp:184
constexpr std::array all_boundary_vertices
Definition grid_types.hpp:239
constexpr std::array all_boundary_edges
Definition grid_types.hpp:249
Kokkos::View< ScalarType ****, Layout > Grid4DDataScalar
Definition grid_types.hpp:25
Kokkos::View< ScalarType **, Layout > Grid2DDataScalar
Definition grid_types.hpp:19
int num_processes()
Definition mpi.hpp:17
int MPIRank
Definition mpi.hpp:8
MPIRank rank()
Definition mpi.hpp:10
double real_t
Definition types.hpp:4
real_t real_c(T t)
Cast to type real_t using "real_c(x)".
Definition types.hpp:8
Definition spherical_shell.hpp:198
Vec3 p00
Definition spherical_shell.hpp:201
int N
Definition spherical_shell.hpp:205
Vec3 pNN
Definition spherical_shell.hpp:204
Vec3 p0N
Definition spherical_shell.hpp:202
Vec3 pN0
Definition spherical_shell.hpp:203
BaseCorners(Vec3 p00_={}, Vec3 p0N_={}, Vec3 pN0_={}, Vec3 pNN_={}, int N_=0)
Definition spherical_shell.hpp:208
dense::Vec< T, 3 > Vec3
Definition spherical_shell.hpp:199
Definition spherical_shell.hpp:2597
int min
Definition spherical_shell.hpp:2599
int max
Definition spherical_shell.hpp:2600
int total
Definition spherical_shell.hpp:2598
double avg
Definition spherical_shell.hpp:2601