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 return local_subdomains( subdomain_to_rank, mpi::rank() );
909 }
910
911 /// @brief Same as the no-arg overload but compares subdomain owners against an explicit rank.
912 ///
913 /// Used when building a DistributedDomain on a sub-communicator: the caller passes
914 /// their sub-comm rank and a subdomain_to_rank function that returns sub-comm-local
915 /// indices, and the returned subdomain list is the one this rank owns on the sub-comm.
916 /// Unlike the no-arg overload, returns an empty vector (not throws) when no subdomains
917 /// land here — that happens legitimately for ranks that are inactive on this level.
918 std::vector< SubdomainInfo >
919 local_subdomains( const SubdomainToRankDistributionFunction& subdomain_to_rank, mpi::MPIRank my_rank ) const
920 {
921 std::vector< SubdomainInfo > subdomains;
922 for ( int diamond_id = 0; diamond_id < 10; diamond_id++ )
923 {
924 for ( int x = 0; x < num_subdomains_per_diamond_side(); x++ )
925 {
926 for ( int y = 0; y < num_subdomains_per_diamond_side(); y++ )
927 {
928 for ( int r = 0; r < num_subdomains_in_radial_direction_; r++ )
929 {
930 SubdomainInfo subdomain( diamond_id, x, y, r );
931
932 const auto target_rank = subdomain_to_rank(
934
935 if ( target_rank == my_rank )
936 {
937 subdomains.push_back( subdomain );
938 }
939 }
940 }
941 }
942 }
943
944 return subdomains;
945 }
946
947 /// @brief Number of global subdomains.
953
954 /// @brief Number of hex cells on the finest level, per subdomain.
955 [[nodiscard]] long long num_micro_hex_cells_per_subdomain() const
956 {
957 return static_cast< long long >( subdomain_num_nodes_per_side_laterally() - 1 ) *
959 }
960
961 /// @brief Number of hex cells on the finest level, globally.
962 [[nodiscard]] long long num_global_micro_hex_cells() const
963 {
965 }
966
967 private:
968 /// Number of times each diamond is refined laterally in each direction.
969 int diamond_lateral_refinement_level_{};
970
971 /// Shell radii.
972 std::vector< double > radii_;
973
974 /// Number of subdomains per diamond (for parallel partitioning) in the lateral direction (at least 1).
975 int num_subdomains_in_lateral_direction_{};
976
977 /// Number of subdomains per diamond (for parallel partitioning) in the radial direction (at least 1).
978 int num_subdomains_in_radial_direction_{};
979};
980
981/// @brief Neighborhood information of a single subdomain.
982///
983/// @note If you want to create a domain for an application, use the \ref terra::grid::shell::DistributedDomain class,
984/// which constructs an instance of this class internally.
985///
986/// Holds information such as the MPI ranks of the neighboring subdomains, and their orientation.
987/// Required for communication (packing, unpacking, sending, receiving 'ghost-layer' data).
988///
989/// **Details on communication**
990///
991/// Data is rotated during unpacking.
992///
993/// *Packing/sending*
994///
995/// Sender just packs data from the grid into a buffer using the (x, y, r) coordinates in order.
996/// For instance: the face boundary xr is packed into a 2D buffer: buffer( x, r ), the face boundary yr is packed
997/// as buffer( y, r ), always iterating locally from 0 to end.
998///
999/// *Unpacking*
1000///
1001/// When a packet from a certain neighbor subdomain arrives, we have the following information
1002/// set up in this class (for instance in the `NeighborSubdomainTupleFace` instances):
1003///
1004/// *Organization*
1005///
1006/// At each boundary face of a local subdomain we store in this class a list of tuples with entries:
1007///
1008/// SubdomainInfo: neighboring subdomain identifier
1009/// BoundaryFace: boundary face of the neighboring subdomain (from its local view)
1010/// UnpackingOrderingFace: information how to iterate over the buffer for each coordinate during unpacking
1011///
1012/// So e.g., the data (for some subdomain):
1013///
1014/// @code
1015/// neighborhood_face_[ F_X1R ] = { neighbor_subdomain_info, F_1YR, (BACKWARD, FORWARD), neighbor_rank }
1016/// @endcode
1017///
1018/// means that for this subdomain, the boundary face `F_X1R` interfaces with the neighbor subdomain
1019/// `neighbor_subdomain_info`, at its boundary `F_1YR`, that is located on rank `neighbor_rank`.
1020/// If we unpack data that we receive from the subdomain, we must invert the iteration over the first
1021/// buffer index, and move forward in the second index.
1022///
1023/// @note See \ref terra::grid::BoundaryVertex, \ref terra::grid::BoundaryEdge, \ref terra::grid::BoundaryFace,
1024/// \ref terra::grid::BoundaryDirection for details on the naming convention of the boundary types like `F_X1R`.
1025/// Roughly: `0 == start`, `1 == end`, `X == varying in x`, `Y == varying in y`, `R == varying in r`.
1026///
1027/// *Execution*
1028///
1029/// Let's assume we receive data from the neighbor specified above.
1030///
1031/// Sender side (with local boundary `F_1YR`):
1032///
1033/// @code
1034/// buffer( y, r ) = send_data( sender_local_subdomain_id, x_size - 1, y, r )
1035/// ^^^^^^^^^^
1036/// == x_end
1037/// == const
1038/// @endcode
1039///
1040/// Receiver side (with local boundary `F_X1R`):
1041///
1042/// @code
1043/// recv_data( receiver_local_subdomain_id, x, y_size - 1, r ) = buffer( x_size - 1 - x, r )
1044/// ^^^^^^^^^^ ^^^^^^^^^^^^^^ ^
1045/// == y_end BACKWARD FORWARD
1046/// == const
1047/// @endcode
1048///
1049/// @note It is due to the structure of the spherical shell mesh that we never have to swap the indices!
1050/// (For vertex boundaries this is anyway not required (0D array) and for edges neither (1D array - we only
1051/// have forward and backward iteration).)
1052/// Thus, it is enough to have the FORWARD/BACKWARD tuple! The radial direction is always radial.
1053/// And if we communicate in the radial direction (i.e., sending "xy-planes") then we never need to swap since
1054/// we are in the same diamond.
1055///
1057{
1058 public:
1060 using UnpackOrderingFace = std::tuple< BoundaryDirection, BoundaryDirection >;
1061
1062 using NeighborSubdomainTupleVertex = std::tuple< SubdomainInfo, BoundaryVertex, mpi::MPIRank >;
1063 using NeighborSubdomainTupleEdge = std::tuple< SubdomainInfo, BoundaryEdge, UnpackOrderingEdge, mpi::MPIRank >;
1064 using NeighborSubdomainTupleFace = std::tuple< SubdomainInfo, BoundaryFace, UnpackOrderingFace, mpi::MPIRank >;
1065
1067
1069 const DomainInfo& domain_info,
1070 const SubdomainInfo& subdomain_info,
1071 const SubdomainToRankDistributionFunction& subdomain_to_rank )
1072 { setup_neighborhood( domain_info, subdomain_info, subdomain_to_rank ); }
1073
1074 const std::map< BoundaryVertex, std::vector< NeighborSubdomainTupleVertex > >& neighborhood_vertex() const
1075 { return neighborhood_vertex_; }
1076
1077 const std::map< BoundaryEdge, std::vector< NeighborSubdomainTupleEdge > >& neighborhood_edge() const
1078 { return neighborhood_edge_; }
1079
1080 const std::map< BoundaryFace, NeighborSubdomainTupleFace >& neighborhood_face() const { return neighborhood_face_; }
1081
1082 private:
1083 void setup_neighborhood_faces( const DomainInfo& domain_info, const SubdomainInfo& subdomain_info )
1084 {
1085 const mpi::MPIRank rank_will_be_overwritten_later = -1;
1086
1087 const int diamond_id = subdomain_info.diamond_id();
1088 const int subdomain_x = subdomain_info.subdomain_x();
1089 const int subdomain_y = subdomain_info.subdomain_y();
1090 const int subdomain_r = subdomain_info.subdomain_r();
1091
1092 const int num_lateral_subdomains = domain_info.num_subdomains_per_diamond_side();
1093 const int num_radial_subdomains = domain_info.num_subdomains_in_radial_direction();
1094
1095 for ( const auto boundary_face : all_boundary_faces )
1096 {
1097 const bool diamond_diamond_boundary =
1098 ( boundary_face == BoundaryFace::F_0YR && subdomain_x == 0 ) ||
1099 ( boundary_face == BoundaryFace::F_1YR && subdomain_x == num_lateral_subdomains - 1 ) ||
1100 ( boundary_face == BoundaryFace::F_X0R && subdomain_y == 0 ) ||
1101 ( boundary_face == BoundaryFace::F_X1R && subdomain_y == num_lateral_subdomains - 1 );
1102
1103 if ( diamond_diamond_boundary )
1104 {
1105 // Node equivalences: part one - communication between diamonds at the same poles
1106 // Iterating forward laterally.
1107
1108 // d_0( 0, :, r ) = d_1( :, 0, r )
1109 // d_1( 0, :, r ) = d_2( :, 0, r )
1110 // d_2( 0, :, r ) = d_3( :, 0, r )
1111 // d_3( 0, :, r ) = d_4( :, 0, r )
1112 // d_4( 0, :, r ) = d_0( :, 0, r )
1113
1114 // d_5( 0, :, r ) = d_6( :, 0, r )
1115 // d_6( 0, :, r ) = d_7( :, 0, r )
1116 // d_7( 0, :, r ) = d_8( :, 0, r )
1117 // d_8( 0, :, r ) = d_9( :, 0, r )
1118 // d_9( 0, :, r ) = d_5( :, 0, r )
1119
1120 // Node equivalences: part two - communication between diamonds at different poles
1121 // Need to go backwards laterally.
1122
1123 // d_0( :, end, r ) = d_5( end, :, r )
1124 // d_1( :, end, r ) = d_6( end, :, r )
1125 // d_2( :, end, r ) = d_7( end, :, r )
1126 // d_3( :, end, r ) = d_8( end, :, r )
1127 // d_4( :, end, r ) = d_9( end, :, r )
1128
1129 // d_5( :, end, r ) = d_1( end, :, r )
1130 // d_6( :, end, r ) = d_2( end, :, r )
1131 // d_7( :, end, r ) = d_3( end, :, r )
1132 // d_8( :, end, r ) = d_4( end, :, r )
1133 // d_9( :, end, r ) = d_0( end, :, r )
1134
1135 switch ( diamond_id )
1136 {
1137 case 0:
1138 case 1:
1139 case 2:
1140 case 3:
1141 case 4:
1142
1143 switch ( boundary_face )
1144 {
1145 // (north-north)
1147 neighborhood_face_[BoundaryFace::F_0YR] = {
1148 SubdomainInfo( ( diamond_id + 1 ) % 5, subdomain_y, 0, subdomain_r ),
1151 rank_will_be_overwritten_later };
1152 break;
1154 neighborhood_face_[BoundaryFace::F_X0R] = {
1155 SubdomainInfo( ( diamond_id + 4 ) % 5, 0, subdomain_x, subdomain_r ),
1158 rank_will_be_overwritten_later };
1159 break;
1160
1161 // (north-south)
1163 neighborhood_face_[BoundaryFace::F_X1R] = {
1164 SubdomainInfo(
1165 diamond_id + 5,
1166 num_lateral_subdomains - 1,
1167 num_lateral_subdomains - 1 - subdomain_x,
1168 subdomain_r ),
1171 rank_will_be_overwritten_later };
1172 break;
1174 neighborhood_face_[BoundaryFace::F_1YR] = {
1175 SubdomainInfo(
1176 ( diamond_id + 4 ) % 5 + 5,
1177 num_lateral_subdomains - 1 - subdomain_y,
1178 num_lateral_subdomains - 1,
1179 subdomain_r ),
1182 rank_will_be_overwritten_later };
1183 break;
1184
1185 default:
1186 Kokkos::abort( "This should not happen." );
1187 }
1188
1189 break;
1190
1191 case 5:
1192 case 6:
1193 case 7:
1194 case 8:
1195 case 9:
1196 switch ( boundary_face )
1197 {
1198 // (south-south)
1200 neighborhood_face_[BoundaryFace::F_0YR] = {
1201 SubdomainInfo( ( diamond_id + 1 ) % 5 + 5, subdomain_y, 0, subdomain_r ),
1204 rank_will_be_overwritten_later };
1205 break;
1207 neighborhood_face_[BoundaryFace::F_X0R] = {
1208 SubdomainInfo( ( diamond_id - 1 ) % 5 + 5, 0, subdomain_x, subdomain_r ),
1211 rank_will_be_overwritten_later };
1212 break;
1213
1214 // (south-north)
1216 neighborhood_face_[BoundaryFace::F_X1R] = {
1217 SubdomainInfo(
1218 ( diamond_id - 4 ) % 5,
1219 num_lateral_subdomains - 1,
1220 num_lateral_subdomains - 1 - subdomain_x,
1221 subdomain_r ),
1224 rank_will_be_overwritten_later };
1225 break;
1227 neighborhood_face_[BoundaryFace::F_1YR] = {
1228 SubdomainInfo(
1229 diamond_id - 5,
1230 num_lateral_subdomains - 1 - subdomain_y,
1231 num_lateral_subdomains - 1,
1232 subdomain_r ),
1235 rank_will_be_overwritten_later };
1236 break;
1237 default:
1238 Kokkos::abort( "This should not happen." );
1239 }
1240 break;
1241
1242 default:
1243 Kokkos::abort( "Invalid diamond id." );
1244 }
1245 }
1246 else
1247 {
1248 // Same diamond.
1249
1250 switch ( boundary_face )
1251 {
1253 neighborhood_face_[boundary_face] = {
1254 SubdomainInfo( diamond_id, subdomain_x - 1, subdomain_y, subdomain_r ),
1257 rank_will_be_overwritten_later };
1258 break;
1260 neighborhood_face_[boundary_face] = {
1261 SubdomainInfo( diamond_id, subdomain_x + 1, subdomain_y, subdomain_r ),
1264 rank_will_be_overwritten_later };
1265 break;
1267 neighborhood_face_[boundary_face] = {
1268 SubdomainInfo( diamond_id, subdomain_x, subdomain_y - 1, subdomain_r ),
1271 rank_will_be_overwritten_later };
1272 break;
1274 neighborhood_face_[boundary_face] = {
1275 SubdomainInfo( diamond_id, subdomain_x, subdomain_y + 1, subdomain_r ),
1278 rank_will_be_overwritten_later };
1279 break;
1281 if ( subdomain_r > 0 )
1282 {
1283 neighborhood_face_[boundary_face] = {
1284 SubdomainInfo( diamond_id, subdomain_x, subdomain_y, subdomain_r - 1 ),
1287 rank_will_be_overwritten_later };
1288 }
1289 break;
1291 if ( subdomain_r < num_radial_subdomains - 1 )
1292 {
1293 neighborhood_face_[boundary_face] = {
1294 SubdomainInfo( diamond_id, subdomain_x, subdomain_y, subdomain_r + 1 ),
1297 rank_will_be_overwritten_later };
1298 }
1299 break;
1300 }
1301 }
1302 }
1303 }
1304
1305 void setup_neighborhood_edges( const DomainInfo& domain_info, const SubdomainInfo& subdomain_info )
1306 {
1307 const int diamond_id = subdomain_info.diamond_id();
1308 const int subdomain_x = subdomain_info.subdomain_x();
1309 const int subdomain_y = subdomain_info.subdomain_y();
1310 const int subdomain_r = subdomain_info.subdomain_r();
1311
1312 const int num_lateral_subdomains = domain_info.num_subdomains_per_diamond_side();
1313 const int num_radial_subdomains = domain_info.num_subdomains_in_radial_direction();
1314
1315 for ( const auto boundary_edge : all_boundary_edges )
1316 {
1317 // Edges in radial direction (beyond diamond boundaries).
1318
1319 if ( diamond_id >= 0 && diamond_id <= 4 )
1320 {
1321 if ( boundary_edge == BoundaryEdge::E_00R )
1322 {
1323 if ( subdomain_x == 0 && subdomain_y == 0 )
1324 {
1325 // North pole
1326 neighborhood_edge_[boundary_edge].emplace_back(
1327 SubdomainInfo( ( diamond_id + 2 ) % 5, 0, 0, subdomain_r ),
1330 -1 );
1331 neighborhood_edge_[boundary_edge].emplace_back(
1332 SubdomainInfo( ( diamond_id + 3 ) % 5, 0, 0, subdomain_r ),
1335 -1 );
1336 }
1337 else if ( subdomain_x == 0 && subdomain_y > 0 )
1338 {
1339 // Northern hemisphere to the right.
1340 neighborhood_edge_[boundary_edge].emplace_back(
1341 SubdomainInfo( ( diamond_id + 1 ) % 5, subdomain_y - 1, 0, subdomain_r ),
1344 -1 );
1345 }
1346 else if ( subdomain_x > 0 && subdomain_y == 0 )
1347 {
1348 // Northern hemisphere to the left.
1349 neighborhood_edge_[boundary_edge].emplace_back(
1350 SubdomainInfo( ( diamond_id + 4 ) % 5, 0, subdomain_x - 1, subdomain_r ),
1353 -1 );
1354 }
1355 }
1356 else if ( boundary_edge == BoundaryEdge::E_01R )
1357 {
1358 if ( subdomain_x == 0 && subdomain_y < num_lateral_subdomains - 1 )
1359 {
1360 // Northern hemisphere to the right
1361 neighborhood_edge_[boundary_edge].emplace_back(
1362 SubdomainInfo( ( diamond_id + 1 ) % 5, subdomain_y + 1, 0, subdomain_r ),
1365 -1 );
1366 }
1367 else if ( subdomain_x > 0 && subdomain_y == num_lateral_subdomains - 1 )
1368 {
1369 // Northern hemisphere to the right and southern
1370 neighborhood_edge_[boundary_edge].emplace_back(
1371 SubdomainInfo(
1372 diamond_id + 5,
1373 num_lateral_subdomains - 1,
1374 num_lateral_subdomains - subdomain_x,
1375 subdomain_r ),
1378 -1 );
1379 }
1380 }
1381 else if ( boundary_edge == BoundaryEdge::E_10R )
1382 {
1383 if ( subdomain_y == 0 && subdomain_x < num_lateral_subdomains - 1 )
1384 {
1385 // Northern hemisphere to the left
1386 neighborhood_edge_[boundary_edge].emplace_back(
1387 SubdomainInfo( ( diamond_id + 4 ) % 5, 0, subdomain_x + 1, subdomain_r ),
1390 -1 );
1391 }
1392 else if ( subdomain_y > 0 && subdomain_x == num_lateral_subdomains - 1 )
1393 {
1394 // Northern hemisphere to the left and southern
1395 neighborhood_edge_[boundary_edge].emplace_back(
1396 SubdomainInfo(
1397 ( diamond_id + 4 ) % 5 + 5,
1398 num_lateral_subdomains - subdomain_y,
1399 num_lateral_subdomains - 1,
1400 subdomain_r ),
1403 -1 );
1404 }
1405 }
1406 else if ( boundary_edge == BoundaryEdge::E_11R )
1407 {
1408 if ( subdomain_x < num_lateral_subdomains - 1 && subdomain_y == num_lateral_subdomains - 1 )
1409 {
1410 // Northern hemi to southern (right)
1411 neighborhood_edge_[boundary_edge].emplace_back(
1412 SubdomainInfo(
1413 diamond_id + 5,
1414 num_lateral_subdomains - 1,
1415 num_lateral_subdomains - subdomain_x - 2,
1416 subdomain_r ),
1419 -1 );
1420 }
1421 else if ( subdomain_y < num_lateral_subdomains - 1 && subdomain_x == num_lateral_subdomains - 1 )
1422 {
1423 // Northern hemi to southern (left)
1424 neighborhood_edge_[boundary_edge].emplace_back(
1425 SubdomainInfo(
1426 ( diamond_id + 4 ) % 5 + 5,
1427 num_lateral_subdomains - subdomain_y - 2,
1428 num_lateral_subdomains - 1,
1429 subdomain_r ),
1432 -1 );
1433 }
1434 }
1435 }
1436 else if ( diamond_id >= 5 && diamond_id <= 9 )
1437 {
1438 if ( boundary_edge == BoundaryEdge::E_00R )
1439 {
1440 if ( subdomain_x == 0 && subdomain_y == 0 )
1441 {
1442 // South pole
1443 neighborhood_edge_[boundary_edge].emplace_back(
1444 SubdomainInfo( ( diamond_id + 2 ) % 5 + 5, 0, 0, subdomain_r ),
1447 -1 );
1448 neighborhood_edge_[boundary_edge].emplace_back(
1449 SubdomainInfo( ( diamond_id + 3 ) % 5 + 5, 0, 0, subdomain_r ),
1452 -1 );
1453 }
1454
1455 if ( subdomain_x == 0 && subdomain_y > 0 )
1456 {
1457 // Southern hemisphere to the right.
1458 neighborhood_edge_[boundary_edge].emplace_back(
1459 SubdomainInfo( ( diamond_id + 1 ) % 5 + 5, subdomain_y - 1, 0, subdomain_r ),
1462 -1 );
1463 }
1464 else if ( subdomain_x > 0 && subdomain_y == 0 )
1465 {
1466 // Southern hemisphere to the left.
1467 neighborhood_edge_[boundary_edge].emplace_back(
1468 SubdomainInfo( ( diamond_id + 4 ) % 5 + 5, 0, subdomain_x - 1, subdomain_r ),
1471 -1 );
1472 }
1473 }
1474
1475 if ( boundary_edge == BoundaryEdge::E_01R )
1476 {
1477 if ( subdomain_x == 0 && subdomain_y < num_lateral_subdomains - 1 )
1478 {
1479 // Southern hemisphere to the right
1480 neighborhood_edge_[boundary_edge].push_back(
1481 { SubdomainInfo( ( diamond_id + 1 ) % 5 + 5, subdomain_y + 1, 0, subdomain_r ),
1484 -1 } );
1485 }
1486 else if ( subdomain_x > 0 && subdomain_y == num_lateral_subdomains - 1 )
1487 {
1488 // Southern hemisphere to the right and north
1489 neighborhood_edge_[boundary_edge].push_back(
1490 { SubdomainInfo(
1491 ( diamond_id + 1 ) % 5,
1492 num_lateral_subdomains - 1,
1493 num_lateral_subdomains - subdomain_x,
1494 subdomain_r ),
1497 -1 } );
1498 }
1499 }
1500
1501 if ( boundary_edge == BoundaryEdge::E_10R )
1502 {
1503 if ( subdomain_y == 0 && subdomain_x < num_lateral_subdomains - 1 )
1504 {
1505 // Southern hemisphere to the left
1506 neighborhood_edge_[boundary_edge].emplace_back(
1507 SubdomainInfo( ( diamond_id - 1 ) % 5 + 5, 0, subdomain_x + 1, subdomain_r ),
1510 -1 );
1511 }
1512 else if ( subdomain_y > 0 && subdomain_x == num_lateral_subdomains - 1 )
1513 {
1514 // Southern hemisphere to the left and northern
1515 neighborhood_edge_[boundary_edge].emplace_back(
1516 SubdomainInfo(
1517 diamond_id - 5,
1518 num_lateral_subdomains - subdomain_y,
1519 num_lateral_subdomains - 1,
1520 subdomain_r ),
1523 -1 );
1524 }
1525 }
1526
1527 if ( boundary_edge == BoundaryEdge::E_11R )
1528 {
1529 if ( subdomain_x < num_lateral_subdomains - 1 && subdomain_y == num_lateral_subdomains - 1 )
1530 {
1531 // Southern hemi to northern (right)
1532 neighborhood_edge_[boundary_edge].emplace_back(
1533 SubdomainInfo(
1534 ( diamond_id - 4 ) % 5,
1535 num_lateral_subdomains - 1,
1536 num_lateral_subdomains - subdomain_x - 2,
1537 subdomain_r ),
1540 -1 );
1541 }
1542 else if ( subdomain_y < num_lateral_subdomains - 1 && subdomain_x == num_lateral_subdomains - 1 )
1543 {
1544 // Southern hemi to northern (left)
1545 neighborhood_edge_[boundary_edge].emplace_back(
1546 SubdomainInfo(
1547 diamond_id - 5,
1548 num_lateral_subdomains - subdomain_y - 2,
1549 num_lateral_subdomains - 1,
1550 subdomain_r ),
1553 -1 );
1554 }
1555 }
1556 }
1557 else
1558 {
1559 Kokkos::abort( "Invalid diamond ID." );
1560 }
1561
1562 // Still beyond diamond boundaries but now lateral edges
1563
1564 if ( diamond_id >= 0 && diamond_id <= 4 )
1565 {
1566 if ( boundary_edge == BoundaryEdge::E_0Y0 && subdomain_x == 0 && subdomain_r > 0 )
1567 {
1568 // Northern hemi to the right + cmb
1569 neighborhood_edge_[boundary_edge].emplace_back(
1570 SubdomainInfo( ( diamond_id + 1 ) % 5, subdomain_y, 0, subdomain_r - 1 ),
1573 -1 );
1574 }
1575 else if (
1576 boundary_edge == BoundaryEdge::E_0Y1 && subdomain_x == 0 &&
1577 subdomain_r < num_radial_subdomains - 1 )
1578 {
1579 // Northern hemi to the right + surface
1580 neighborhood_edge_[boundary_edge].emplace_back(
1581 SubdomainInfo( ( diamond_id + 1 ) % 5, subdomain_y, 0, subdomain_r + 1 ),
1584 -1 );
1585 }
1586 else if ( boundary_edge == BoundaryEdge::E_X00 && subdomain_y == 0 && subdomain_r > 0 )
1587 {
1588 // Northern hemi to the left + cmb
1589 neighborhood_edge_[boundary_edge].emplace_back(
1590 SubdomainInfo( ( diamond_id + 4 ) % 5, 0, subdomain_x, subdomain_r - 1 ),
1593 -1 );
1594 }
1595 else if (
1596 boundary_edge == BoundaryEdge::E_X01 && subdomain_y == 0 &&
1597 subdomain_r < num_radial_subdomains - 1 )
1598 {
1599 // Northern hemi to the left + surface
1600 neighborhood_edge_[boundary_edge].emplace_back(
1601 SubdomainInfo( ( diamond_id + 4 ) % 5, 0, subdomain_x, subdomain_r + 1 ),
1604 -1 );
1605 }
1606
1607 // Northern to southern
1608 if ( boundary_edge == BoundaryEdge::E_X10 && subdomain_y == num_lateral_subdomains - 1 &&
1609 subdomain_r > 0 )
1610 {
1611 // Northern hemi to the bottom right + cmb
1612 neighborhood_edge_[boundary_edge].emplace_back(
1613 SubdomainInfo(
1614 diamond_id + 5,
1615 num_lateral_subdomains - 1,
1616 num_lateral_subdomains - 1 - subdomain_x,
1617 subdomain_r - 1 ),
1620 -1 );
1621 }
1622 else if (
1623 boundary_edge == BoundaryEdge::E_X11 && subdomain_y == num_lateral_subdomains - 1 &&
1624 subdomain_r < num_radial_subdomains - 1 )
1625 {
1626 // Northern hemi to the bottom right + surface
1627 neighborhood_edge_[boundary_edge].emplace_back(
1628 SubdomainInfo(
1629 diamond_id + 5,
1630 num_lateral_subdomains - 1,
1631 num_lateral_subdomains - 1 - subdomain_x,
1632 subdomain_r + 1 ),
1635 -1 );
1636 }
1637 else if (
1638 boundary_edge == BoundaryEdge::E_1Y0 && subdomain_x == num_lateral_subdomains - 1 &&
1639 subdomain_r > 0 )
1640 {
1641 // Northern hemi to the bottom left + cmb
1642 neighborhood_edge_[boundary_edge].emplace_back(
1643 SubdomainInfo(
1644 ( diamond_id + 4 ) % 5 + 5,
1645 num_lateral_subdomains - 1 - subdomain_y,
1646 num_lateral_subdomains - 1,
1647 subdomain_r - 1 ),
1650 -1 );
1651 }
1652 else if (
1653 boundary_edge == BoundaryEdge::E_1Y1 && subdomain_x == num_lateral_subdomains - 1 &&
1654 subdomain_r < num_radial_subdomains - 1 )
1655 {
1656 // Northern hemi to the bottom left + surface
1657 neighborhood_edge_[boundary_edge].emplace_back(
1658 SubdomainInfo(
1659 ( diamond_id + 4 ) % 5 + 5,
1660 num_lateral_subdomains - 1 - subdomain_y,
1661 num_lateral_subdomains - 1,
1662 subdomain_r + 1 ),
1665 -1 );
1666 }
1667 }
1668 else if ( diamond_id >= 5 && diamond_id <= 9 )
1669 {
1670 if ( boundary_edge == BoundaryEdge::E_0Y0 && subdomain_x == 0 && subdomain_r > 0 )
1671 {
1672 // Southern hemi to the right + cmb
1673 neighborhood_edge_[boundary_edge].emplace_back(
1674 SubdomainInfo( ( diamond_id + 1 ) % 5 + 5, subdomain_y, 0, subdomain_r - 1 ),
1677 -1 );
1678 }
1679 else if (
1680 boundary_edge == BoundaryEdge::E_0Y1 && subdomain_x == 0 &&
1681 subdomain_r < num_radial_subdomains - 1 )
1682 {
1683 // Southern hemi to the right + surface
1684 neighborhood_edge_[boundary_edge].emplace_back(
1685 SubdomainInfo( ( diamond_id + 1 ) % 5 + 5, subdomain_y, 0, subdomain_r + 1 ),
1688 -1 );
1689 }
1690 else if ( boundary_edge == BoundaryEdge::E_X00 && subdomain_y == 0 && subdomain_r > 0 )
1691 {
1692 // Southern hemi to the left + cmb
1693 neighborhood_edge_[boundary_edge].emplace_back(
1694 SubdomainInfo( ( diamond_id - 1 ) % 5 + 5, 0, subdomain_x, subdomain_r - 1 ),
1697 -1 );
1698 }
1699 else if (
1700 boundary_edge == BoundaryEdge::E_X01 && subdomain_y == 0 &&
1701 subdomain_r < num_radial_subdomains - 1 )
1702 {
1703 // Southern hemi to the left + surface
1704 neighborhood_edge_[boundary_edge].emplace_back(
1705 SubdomainInfo( ( diamond_id - 1 ) % 5 + 5, 0, subdomain_x, subdomain_r + 1 ),
1708 -1 );
1709 }
1710
1711 // Southern to northern
1712 if ( boundary_edge == BoundaryEdge::E_X10 && subdomain_y == num_lateral_subdomains - 1 &&
1713 subdomain_r > 0 )
1714 {
1715 // Southern hemi to the top right + cmb
1716 neighborhood_edge_[boundary_edge].emplace_back(
1717 SubdomainInfo(
1718 ( diamond_id - 4 ) % 5,
1719 num_lateral_subdomains - 1,
1720 num_lateral_subdomains - 1 - subdomain_x,
1721 subdomain_r - 1 ),
1724 -1 );
1725 }
1726 else if (
1727 boundary_edge == BoundaryEdge::E_X11 && subdomain_y == num_lateral_subdomains - 1 &&
1728 subdomain_r < num_radial_subdomains - 1 )
1729 {
1730 // Southern hemi to the top right + surface
1731 neighborhood_edge_[boundary_edge].emplace_back(
1732 SubdomainInfo(
1733 ( diamond_id - 4 ) % 5,
1734 num_lateral_subdomains - 1,
1735 num_lateral_subdomains - 1 - subdomain_x,
1736 subdomain_r + 1 ),
1739 -1 );
1740 }
1741 else if (
1742 boundary_edge == BoundaryEdge::E_1Y0 && subdomain_x == num_lateral_subdomains - 1 &&
1743 subdomain_r > 0 )
1744 {
1745 // Southern hemi to the top left + cmb
1746 neighborhood_edge_[boundary_edge].emplace_back(
1747 SubdomainInfo(
1748 diamond_id - 5,
1749 num_lateral_subdomains - 1 - subdomain_y,
1750 num_lateral_subdomains - 1,
1751 subdomain_r - 1 ),
1754 -1 );
1755 }
1756 else if (
1757 boundary_edge == BoundaryEdge::E_1Y1 && subdomain_x == num_lateral_subdomains - 1 &&
1758 subdomain_r < num_radial_subdomains - 1 )
1759 {
1760 // Southern hemi to the top left + surface
1761 neighborhood_edge_[boundary_edge].emplace_back(
1762 SubdomainInfo(
1763 diamond_id - 5,
1764 num_lateral_subdomains - 1 - subdomain_y,
1765 num_lateral_subdomains - 1,
1766 subdomain_r + 1 ),
1769 -1 );
1770 }
1771 }
1772 else
1773 {
1774 Kokkos::abort( "Invalid diamond ID" );
1775 }
1776
1777 // Now only same diamond cases
1778
1779 // Edges in radial direction
1780
1781 if ( boundary_edge == BoundaryEdge::E_00R && subdomain_x > 0 && subdomain_y > 0 )
1782 {
1783 neighborhood_edge_[boundary_edge].emplace_back(
1784 SubdomainInfo( diamond_id, subdomain_x - 1, subdomain_y - 1, subdomain_r ),
1787 -1 );
1788 }
1789 else if (
1790 boundary_edge == BoundaryEdge::E_10R && subdomain_x < num_lateral_subdomains - 1 && subdomain_y > 0 )
1791 {
1792 neighborhood_edge_[boundary_edge].emplace_back(
1793 SubdomainInfo( diamond_id, subdomain_x + 1, subdomain_y - 1, subdomain_r ),
1796 -1 );
1797 }
1798 else if (
1799 boundary_edge == BoundaryEdge::E_01R && subdomain_x > 0 && subdomain_y < num_lateral_subdomains - 1 )
1800 {
1801 neighborhood_edge_[boundary_edge].emplace_back(
1802 SubdomainInfo( diamond_id, subdomain_x - 1, subdomain_y + 1, subdomain_r ),
1805 -1 );
1806 }
1807 else if (
1808 boundary_edge == BoundaryEdge::E_11R && subdomain_x < num_lateral_subdomains - 1 &&
1809 subdomain_y < num_lateral_subdomains - 1 )
1810 {
1811 neighborhood_edge_[boundary_edge].emplace_back(
1812 SubdomainInfo( diamond_id, subdomain_x + 1, subdomain_y + 1, subdomain_r ),
1815 -1 );
1816 }
1817
1818 // Edges in Y direction
1819
1820 else if ( boundary_edge == BoundaryEdge::E_0Y0 && subdomain_x > 0 && subdomain_r > 0 )
1821 {
1822 neighborhood_edge_[boundary_edge].emplace_back(
1823 SubdomainInfo( diamond_id, subdomain_x - 1, subdomain_y, subdomain_r - 1 ),
1826 -1 );
1827 }
1828 else if (
1829 boundary_edge == BoundaryEdge::E_1Y0 && subdomain_x < num_lateral_subdomains - 1 && subdomain_r > 0 )
1830 {
1831 neighborhood_edge_[boundary_edge].emplace_back(
1832 SubdomainInfo( diamond_id, subdomain_x + 1, subdomain_y, subdomain_r - 1 ),
1835 -1 );
1836 }
1837 else if (
1838 boundary_edge == BoundaryEdge::E_0Y1 && subdomain_x > 0 && subdomain_r < num_radial_subdomains - 1 )
1839 {
1840 neighborhood_edge_[boundary_edge].emplace_back(
1841 SubdomainInfo( diamond_id, subdomain_x - 1, subdomain_y, subdomain_r + 1 ),
1844 -1 );
1845 }
1846 else if (
1847 boundary_edge == BoundaryEdge::E_1Y1 && subdomain_x < num_lateral_subdomains - 1 &&
1848 subdomain_r < num_radial_subdomains - 1 )
1849 {
1850 neighborhood_edge_[boundary_edge].emplace_back(
1851 SubdomainInfo( diamond_id, subdomain_x + 1, subdomain_y, subdomain_r + 1 ),
1854 -1 );
1855 }
1856
1857 // Radial (Y fixed)
1858
1859 else if ( boundary_edge == BoundaryEdge::E_X00 && subdomain_y > 0 && subdomain_r > 0 )
1860 {
1861 neighborhood_edge_[boundary_edge].emplace_back(
1862 SubdomainInfo( diamond_id, subdomain_x, subdomain_y - 1, subdomain_r - 1 ),
1865 -1 );
1866 }
1867 else if (
1868 boundary_edge == BoundaryEdge::E_X10 && subdomain_y < num_lateral_subdomains - 1 && subdomain_r > 0 )
1869 {
1870 neighborhood_edge_[boundary_edge].emplace_back(
1871 SubdomainInfo( diamond_id, subdomain_x, subdomain_y + 1, subdomain_r - 1 ),
1874 -1 );
1875 }
1876 else if (
1877 boundary_edge == BoundaryEdge::E_X01 && subdomain_y > 0 && subdomain_r < num_radial_subdomains - 1 )
1878 {
1879 neighborhood_edge_[boundary_edge].emplace_back(
1880 SubdomainInfo( diamond_id, subdomain_x, subdomain_y - 1, subdomain_r + 1 ),
1883 -1 );
1884 }
1885 else if (
1886 boundary_edge == BoundaryEdge::E_X11 && subdomain_y < num_lateral_subdomains - 1 &&
1887 subdomain_r < num_radial_subdomains - 1 )
1888 {
1889 neighborhood_edge_[boundary_edge].emplace_back(
1890 SubdomainInfo( diamond_id, subdomain_x, subdomain_y + 1, subdomain_r + 1 ),
1893 -1 );
1894 }
1895 }
1896 }
1897
1898 void setup_neighborhood_vertices( const DomainInfo& domain_info, const SubdomainInfo& subdomain_info )
1899 {
1900 const int diamond_id = subdomain_info.diamond_id();
1901 const int subdomain_x = subdomain_info.subdomain_x();
1902 const int subdomain_y = subdomain_info.subdomain_y();
1903 const int subdomain_r = subdomain_info.subdomain_r();
1904
1905 const int num_lateral_subdomains = domain_info.num_subdomains_per_diamond_side();
1906 const int num_radial_subdomains = domain_info.num_subdomains_in_radial_direction();
1907
1908 for ( const auto boundary_vertex : all_boundary_vertices )
1909 {
1910 // Across diamond boundaries
1911 if ( diamond_id >= 0 && diamond_id <= 4 )
1912 {
1913 {
1914 // north pole
1915 if ( boundary_vertex == BoundaryVertex::V_000 && subdomain_x == 0 && subdomain_y == 0 &&
1916 subdomain_r > 0 )
1917 {
1918 neighborhood_vertex_[boundary_vertex].emplace_back(
1919 SubdomainInfo( ( diamond_id + 2 ) % 5, 0, 0, subdomain_r - 1 ), BoundaryVertex::V_001, -1 );
1920 neighborhood_vertex_[boundary_vertex].emplace_back(
1921 SubdomainInfo( ( diamond_id + 3 ) % 5, 0, 0, subdomain_r - 1 ), BoundaryVertex::V_001, -1 );
1922 }
1923
1924 if ( boundary_vertex == BoundaryVertex::V_001 && subdomain_x == 0 && subdomain_y == 0 &&
1925 subdomain_r < num_radial_subdomains - 1 )
1926 {
1927 neighborhood_vertex_[boundary_vertex].emplace_back(
1928 SubdomainInfo( ( diamond_id + 2 ) % 5, 0, 0, subdomain_r + 1 ), BoundaryVertex::V_000, -1 );
1929 neighborhood_vertex_[boundary_vertex].emplace_back(
1930 SubdomainInfo( ( diamond_id + 3 ) % 5, 0, 0, subdomain_r + 1 ), BoundaryVertex::V_000, -1 );
1931 }
1932 }
1933
1934 {
1935 // Northern hemisphere to the right.
1936
1937 if ( boundary_vertex == BoundaryVertex::V_000 && subdomain_x == 0 && subdomain_y > 0 &&
1938 subdomain_r > 0 )
1939 {
1940 neighborhood_vertex_[boundary_vertex].emplace_back(
1941 SubdomainInfo( ( diamond_id + 1 ) % 5, subdomain_y - 1, 0, subdomain_r - 1 ),
1943 -1 );
1944 }
1945 else if (
1946 boundary_vertex == BoundaryVertex::V_001 && subdomain_x == 0 && subdomain_y > 0 &&
1947 subdomain_r < num_radial_subdomains - 1 )
1948 {
1949 neighborhood_vertex_[boundary_vertex].emplace_back(
1950 SubdomainInfo( ( diamond_id + 1 ) % 5, subdomain_y - 1, 0, subdomain_r + 1 ),
1952 -1 );
1953 }
1954 }
1955
1956 {
1957 // Northern hemisphere to the left.
1958
1959 if ( boundary_vertex == BoundaryVertex::V_000 && subdomain_x > 0 && subdomain_y == 0 &&
1960 subdomain_r > 0 )
1961 {
1962 neighborhood_vertex_[boundary_vertex].emplace_back(
1963 SubdomainInfo( ( diamond_id + 4 ) % 5, 0, subdomain_x - 1, subdomain_r - 1 ),
1965 -1 );
1966 }
1967 else if (
1968 boundary_vertex == BoundaryVertex::V_001 && subdomain_x > 0 && subdomain_y == 0 &&
1969 subdomain_r < num_radial_subdomains - 1 )
1970 {
1971 neighborhood_vertex_[boundary_vertex].emplace_back(
1972 SubdomainInfo( ( diamond_id + 4 ) % 5, 0, subdomain_x - 1, subdomain_r + 1 ),
1974 -1 );
1975 }
1976 }
1977
1978 {
1979 // Northern hemisphere to the right
1980
1981 if ( boundary_vertex == BoundaryVertex::V_010 && subdomain_x == 0 &&
1982 subdomain_y < num_lateral_subdomains - 1 && subdomain_r > 0 )
1983 {
1984 neighborhood_vertex_[boundary_vertex].emplace_back(
1985 SubdomainInfo( ( diamond_id + 1 ) % 5, subdomain_y + 1, 0, subdomain_r - 1 ),
1987 -1 );
1988 }
1989 else if (
1990 boundary_vertex == BoundaryVertex::V_011 && subdomain_x == 0 &&
1991 subdomain_y < num_lateral_subdomains - 1 && subdomain_r < num_radial_subdomains - 1 )
1992 {
1993 neighborhood_vertex_[boundary_vertex].emplace_back(
1994 SubdomainInfo( ( diamond_id + 1 ) % 5, subdomain_y + 1, 0, subdomain_r + 1 ),
1996 -1 );
1997 }
1998 }
1999
2000 {
2001 // Northern hemisphere to the right and southern
2002
2003 if ( boundary_vertex == BoundaryVertex::V_010 && subdomain_x > 0 &&
2004 subdomain_y == num_lateral_subdomains - 1 && subdomain_r > 0 )
2005 {
2006 neighborhood_vertex_[boundary_vertex].emplace_back(
2007 SubdomainInfo(
2008 diamond_id + 5,
2009 num_lateral_subdomains - 1,
2010 num_lateral_subdomains - subdomain_x,
2011 subdomain_r - 1 ),
2013 -1 );
2014 }
2015 else if (
2016 boundary_vertex == BoundaryVertex::V_011 && subdomain_x > 0 &&
2017 subdomain_y == num_lateral_subdomains - 1 && subdomain_r < num_radial_subdomains - 1 )
2018 {
2019 neighborhood_vertex_[boundary_vertex].emplace_back(
2020 SubdomainInfo(
2021 diamond_id + 5,
2022 num_lateral_subdomains - 1,
2023 num_lateral_subdomains - subdomain_x,
2024 subdomain_r + 1 ),
2026 -1 );
2027 }
2028 }
2029
2030 {
2031 // Northern hemisphere to the left
2032
2033 if ( boundary_vertex == BoundaryVertex::V_100 && subdomain_y == 0 &&
2034 subdomain_x < num_lateral_subdomains - 1 && subdomain_r > 0 )
2035 {
2036 neighborhood_vertex_[boundary_vertex].emplace_back(
2037 SubdomainInfo( ( diamond_id + 4 ) % 5, 0, subdomain_x + 1, subdomain_r - 1 ),
2039 -1 );
2040 }
2041 else if (
2042 boundary_vertex == BoundaryVertex::V_101 && subdomain_y == 0 &&
2043 subdomain_x < num_lateral_subdomains - 1 && subdomain_r < num_radial_subdomains - 1 )
2044 {
2045 neighborhood_vertex_[boundary_vertex].emplace_back(
2046 SubdomainInfo( ( diamond_id + 4 ) % 5, 0, subdomain_x + 1, subdomain_r + 1 ),
2048 -1 );
2049 }
2050 }
2051
2052 {
2053 // Northern hemisphere to the left and southern
2054
2055 if ( boundary_vertex == BoundaryVertex::V_100 && subdomain_y > 0 &&
2056 subdomain_x == num_lateral_subdomains - 1 && subdomain_r > 0 )
2057 {
2058 neighborhood_vertex_[boundary_vertex].emplace_back(
2059 SubdomainInfo(
2060 ( diamond_id + 4 ) % 5 + 5,
2061 num_lateral_subdomains - subdomain_y,
2062 num_lateral_subdomains - 1,
2063 subdomain_r - 1 ),
2065 -1 );
2066 }
2067 else if (
2068 boundary_vertex == BoundaryVertex::V_101 && subdomain_y > 0 &&
2069 subdomain_x == num_lateral_subdomains - 1 && subdomain_r < num_radial_subdomains - 1 )
2070 {
2071 neighborhood_vertex_[boundary_vertex].emplace_back(
2072 SubdomainInfo(
2073 ( diamond_id + 4 ) % 5 + 5,
2074 num_lateral_subdomains - subdomain_y,
2075 num_lateral_subdomains - 1,
2076 subdomain_r + 1 ),
2078 -1 );
2079 }
2080 }
2081
2082 {
2083 // Northern hemi to southern (right)
2084
2085 if ( boundary_vertex == BoundaryVertex::V_110 && subdomain_x < num_lateral_subdomains - 1 &&
2086 subdomain_y == num_lateral_subdomains - 1 && subdomain_r > 0 )
2087 {
2088 neighborhood_vertex_[boundary_vertex].emplace_back(
2089 SubdomainInfo(
2090 diamond_id + 5,
2091 num_lateral_subdomains - 1,
2092 num_lateral_subdomains - subdomain_x - 2,
2093 subdomain_r - 1 ),
2095 -1 );
2096 }
2097 else if (
2098 boundary_vertex == BoundaryVertex::V_111 && subdomain_x < num_lateral_subdomains - 1 &&
2099 subdomain_y == num_lateral_subdomains - 1 && subdomain_r < num_radial_subdomains - 1 )
2100 {
2101 neighborhood_vertex_[boundary_vertex].emplace_back(
2102 SubdomainInfo(
2103 diamond_id + 5,
2104 num_lateral_subdomains - 1,
2105 num_lateral_subdomains - subdomain_x - 2,
2106 subdomain_r + 1 ),
2108 -1 );
2109 }
2110 }
2111
2112 {
2113 // Northern hemi to southern (left)
2114
2115 if ( boundary_vertex == BoundaryVertex::V_110 && subdomain_y < num_lateral_subdomains - 1 &&
2116 subdomain_x == num_lateral_subdomains - 1 && subdomain_r > 0 )
2117 {
2118 neighborhood_vertex_[boundary_vertex].emplace_back(
2119 SubdomainInfo(
2120 ( diamond_id + 4 ) % 5 + 5,
2121 num_lateral_subdomains - subdomain_y - 2,
2122 num_lateral_subdomains - 1,
2123 subdomain_r - 1 ),
2125 -1 );
2126 }
2127 else if (
2128 boundary_vertex == BoundaryVertex::V_111 && subdomain_y < num_lateral_subdomains - 1 &&
2129 subdomain_x == num_lateral_subdomains - 1 && subdomain_r < num_radial_subdomains - 1 )
2130 {
2131 neighborhood_vertex_[boundary_vertex].emplace_back(
2132 SubdomainInfo(
2133 ( diamond_id + 4 ) % 5 + 5,
2134 num_lateral_subdomains - subdomain_y - 2,
2135 num_lateral_subdomains - 1,
2136 subdomain_r + 1 ),
2138 -1 );
2139 }
2140 }
2141 }
2142 else if ( diamond_id >= 5 && diamond_id <= 9 )
2143 {
2144 {
2145 // south pole
2146 if ( boundary_vertex == BoundaryVertex::V_000 && subdomain_x == 0 && subdomain_y == 0 &&
2147 subdomain_r > 0 )
2148 {
2149 neighborhood_vertex_[boundary_vertex].emplace_back(
2150 SubdomainInfo( ( diamond_id + 2 ) % 5 + 5, 0, 0, subdomain_r - 1 ),
2152 -1 );
2153 neighborhood_vertex_[boundary_vertex].emplace_back(
2154 SubdomainInfo( ( diamond_id + 3 ) % 5 + 5, 0, 0, subdomain_r - 1 ),
2156 -1 );
2157 }
2158
2159 if ( boundary_vertex == BoundaryVertex::V_001 && subdomain_x == 0 && subdomain_y == 0 &&
2160 subdomain_r < num_radial_subdomains - 1 )
2161 {
2162 neighborhood_vertex_[boundary_vertex].emplace_back(
2163 SubdomainInfo( ( diamond_id + 2 ) % 5 + 5, 0, 0, subdomain_r + 1 ),
2165 -1 );
2166 neighborhood_vertex_[boundary_vertex].emplace_back(
2167 SubdomainInfo( ( diamond_id + 3 ) % 5 + 5, 0, 0, subdomain_r + 1 ),
2169 -1 );
2170 }
2171 }
2172
2173 {
2174 // Southern hemisphere to the right.
2175
2176 if ( boundary_vertex == BoundaryVertex::V_000 && subdomain_x == 0 && subdomain_y > 0 &&
2177 subdomain_r > 0 )
2178 {
2179 neighborhood_vertex_[boundary_vertex].emplace_back(
2180 SubdomainInfo( ( diamond_id + 1 ) % 5 + 5, subdomain_y - 1, 0, subdomain_r - 1 ),
2182 -1 );
2183 }
2184 else if (
2185 boundary_vertex == BoundaryVertex::V_001 && subdomain_x == 0 && subdomain_y > 0 &&
2186 subdomain_r < num_radial_subdomains - 1 )
2187 {
2188 neighborhood_vertex_[boundary_vertex].emplace_back(
2189 SubdomainInfo( ( diamond_id + 1 ) % 5 + 5, subdomain_y - 1, 0, subdomain_r + 1 ),
2191 -1 );
2192 }
2193 }
2194
2195 {
2196 // Southern hemisphere to the left.
2197
2198 if ( boundary_vertex == BoundaryVertex::V_000 && subdomain_x > 0 && subdomain_y == 0 &&
2199 subdomain_r > 0 )
2200 {
2201 neighborhood_vertex_[boundary_vertex].emplace_back(
2202 SubdomainInfo( ( diamond_id + 4 ) % 5 + 5, 0, subdomain_x - 1, subdomain_r - 1 ),
2204 -1 );
2205 }
2206 else if (
2207 boundary_vertex == BoundaryVertex::V_001 && subdomain_x > 0 && subdomain_y == 0 &&
2208 subdomain_r < num_radial_subdomains - 1 )
2209 {
2210 neighborhood_vertex_[boundary_vertex].emplace_back(
2211 SubdomainInfo( ( diamond_id + 4 ) % 5 + 5, 0, subdomain_x - 1, subdomain_r + 1 ),
2213 -1 );
2214 }
2215 }
2216
2217 {
2218 // Southern hemisphere to the right
2219
2220 if ( boundary_vertex == BoundaryVertex::V_010 && subdomain_x == 0 &&
2221 subdomain_y < num_lateral_subdomains - 1 && subdomain_r > 0 )
2222 {
2223 neighborhood_vertex_[boundary_vertex].emplace_back(
2224 SubdomainInfo( ( diamond_id + 1 ) % 5 + 5, subdomain_y + 1, 0, subdomain_r - 1 ),
2226 -1 );
2227 }
2228 else if (
2229 boundary_vertex == BoundaryVertex::V_011 && subdomain_x == 0 &&
2230 subdomain_y < num_lateral_subdomains - 1 && subdomain_r < num_radial_subdomains - 1 )
2231 {
2232 neighborhood_vertex_[boundary_vertex].emplace_back(
2233 SubdomainInfo( ( diamond_id + 1 ) % 5 + 5, subdomain_y + 1, 0, subdomain_r + 1 ),
2235 -1 );
2236 }
2237 }
2238
2239 {
2240 // Southern hemisphere to the right and north
2241
2242 if ( boundary_vertex == BoundaryVertex::V_010 && subdomain_x > 0 &&
2243 subdomain_y == num_lateral_subdomains - 1 && subdomain_r > 0 )
2244 {
2245 neighborhood_vertex_[boundary_vertex].emplace_back(
2246 SubdomainInfo(
2247 ( diamond_id + 1 ) % 5,
2248 num_lateral_subdomains - 1,
2249 num_lateral_subdomains - subdomain_x,
2250 subdomain_r - 1 ),
2252 -1 );
2253 }
2254 else if (
2255 boundary_vertex == BoundaryVertex::V_011 && subdomain_x > 0 &&
2256 subdomain_y == num_lateral_subdomains - 1 && subdomain_r < num_radial_subdomains - 1 )
2257 {
2258 neighborhood_vertex_[boundary_vertex].emplace_back(
2259 SubdomainInfo(
2260 ( diamond_id + 1 ) % 5,
2261 num_lateral_subdomains - 1,
2262 num_lateral_subdomains - subdomain_x,
2263 subdomain_r + 1 ),
2265 -1 );
2266 }
2267 }
2268
2269 {
2270 // Southern hemisphere to the left
2271
2272 if ( boundary_vertex == BoundaryVertex::V_100 && subdomain_y == 0 &&
2273 subdomain_x < num_lateral_subdomains - 1 && subdomain_r > 0 )
2274 {
2275 neighborhood_vertex_[boundary_vertex].emplace_back(
2276 SubdomainInfo( ( diamond_id - 1 ) % 5 + 5, 0, subdomain_x + 1, subdomain_r - 1 ),
2278 -1 );
2279 }
2280 else if (
2281 boundary_vertex == BoundaryVertex::V_101 && subdomain_y == 0 &&
2282 subdomain_x < num_lateral_subdomains - 1 && subdomain_r < num_radial_subdomains - 1 )
2283 {
2284 neighborhood_vertex_[boundary_vertex].emplace_back(
2285 SubdomainInfo( ( diamond_id - 1 ) % 5 + 5, 0, subdomain_x + 1, subdomain_r + 1 ),
2287 -1 );
2288 }
2289 }
2290
2291 {
2292 // Southern hemisphere to the left and northern
2293
2294 if ( boundary_vertex == BoundaryVertex::V_100 && subdomain_y > 0 &&
2295 subdomain_x == num_lateral_subdomains - 1 && subdomain_r > 0 )
2296 {
2297 neighborhood_vertex_[boundary_vertex].emplace_back(
2298 SubdomainInfo(
2299 diamond_id - 5,
2300 num_lateral_subdomains - subdomain_y,
2301 num_lateral_subdomains - 1,
2302 subdomain_r - 1 ),
2304 -1 );
2305 }
2306 else if (
2307 boundary_vertex == BoundaryVertex::V_101 && subdomain_y > 0 &&
2308 subdomain_x == num_lateral_subdomains - 1 && subdomain_r < num_radial_subdomains - 1 )
2309 {
2310 neighborhood_vertex_[boundary_vertex].emplace_back(
2311 SubdomainInfo(
2312 diamond_id - 5,
2313 num_lateral_subdomains - subdomain_y,
2314 num_lateral_subdomains - 1,
2315 subdomain_r + 1 ),
2317 -1 );
2318 }
2319 }
2320
2321 {
2322 // Southern hemi to northern (right)
2323
2324 if ( boundary_vertex == BoundaryVertex::V_110 && subdomain_x < num_lateral_subdomains - 1 &&
2325 subdomain_y == num_lateral_subdomains - 1 && subdomain_r > 0 )
2326 {
2327 neighborhood_vertex_[boundary_vertex].emplace_back(
2328 SubdomainInfo(
2329 ( diamond_id - 4 ) % 5,
2330 num_lateral_subdomains - 1,
2331 num_lateral_subdomains - subdomain_x - 2,
2332 subdomain_r - 1 ),
2334 -1 );
2335 }
2336 else if (
2337 boundary_vertex == BoundaryVertex::V_111 && subdomain_x < num_lateral_subdomains - 1 &&
2338 subdomain_y == num_lateral_subdomains - 1 && subdomain_r < num_radial_subdomains - 1 )
2339 {
2340 neighborhood_vertex_[boundary_vertex].emplace_back(
2341 SubdomainInfo(
2342 ( diamond_id - 4 ) % 5,
2343 num_lateral_subdomains - 1,
2344 num_lateral_subdomains - subdomain_x - 2,
2345 subdomain_r + 1 ),
2347 -1 );
2348 }
2349 }
2350
2351 {
2352 // Southern hemi to northern (left)
2353 if ( boundary_vertex == BoundaryVertex::V_110 && subdomain_y < num_lateral_subdomains - 1 &&
2354 subdomain_x == num_lateral_subdomains - 1 && subdomain_r > 0 )
2355 {
2356 neighborhood_vertex_[boundary_vertex].emplace_back(
2357 SubdomainInfo(
2358 diamond_id - 5,
2359 num_lateral_subdomains - subdomain_y - 2,
2360 num_lateral_subdomains - 1,
2361 subdomain_r - 1 ),
2363 -1 );
2364 }
2365 else if (
2366 boundary_vertex == BoundaryVertex::V_111 && subdomain_y < num_lateral_subdomains - 1 &&
2367 subdomain_x == num_lateral_subdomains - 1 && subdomain_r < num_radial_subdomains - 1 )
2368 {
2369 neighborhood_vertex_[boundary_vertex].emplace_back(
2370 SubdomainInfo(
2371 diamond_id - 5,
2372 num_lateral_subdomains - subdomain_y - 2,
2373 num_lateral_subdomains - 1,
2374 subdomain_r + 1 ),
2376 -1 );
2377 }
2378 }
2379 }
2380 else
2381 {
2382 Kokkos::abort( "Invalid diamond ID." );
2383 }
2384
2385 // Same diamond:
2386
2387 if ( boundary_vertex == BoundaryVertex::V_000 && subdomain_x > 0 && subdomain_y > 0 && subdomain_r > 0 )
2388 {
2389 neighborhood_vertex_[boundary_vertex].emplace_back(
2390 SubdomainInfo( diamond_id, subdomain_x - 1, subdomain_y - 1, subdomain_r - 1 ),
2392 -1 );
2393 }
2394 else if (
2395 boundary_vertex == BoundaryVertex::V_100 && subdomain_x < num_lateral_subdomains - 1 &&
2396 subdomain_y > 0 && subdomain_r > 0 )
2397 {
2398 neighborhood_vertex_[boundary_vertex].emplace_back(
2399 SubdomainInfo( diamond_id, subdomain_x + 1, subdomain_y - 1, subdomain_r - 1 ),
2401 -1 );
2402 }
2403 else if (
2404 boundary_vertex == BoundaryVertex::V_010 && subdomain_x > 0 &&
2405 subdomain_y < num_lateral_subdomains - 1 && subdomain_r > 0 )
2406 {
2407 neighborhood_vertex_[boundary_vertex].emplace_back(
2408 SubdomainInfo( diamond_id, subdomain_x - 1, subdomain_y + 1, subdomain_r - 1 ),
2410 -1 );
2411 }
2412 else if (
2413 boundary_vertex == BoundaryVertex::V_001 && subdomain_x > 0 && subdomain_y > 0 &&
2414 subdomain_r < num_radial_subdomains - 1 )
2415 {
2416 neighborhood_vertex_[boundary_vertex].emplace_back(
2417 SubdomainInfo( diamond_id, subdomain_x - 1, subdomain_y - 1, subdomain_r + 1 ),
2419 -1 );
2420 }
2421 else if (
2422 boundary_vertex == BoundaryVertex::V_110 && subdomain_x < num_lateral_subdomains - 1 &&
2423 subdomain_y < num_lateral_subdomains - 1 && subdomain_r > 0 )
2424 {
2425 neighborhood_vertex_[boundary_vertex].emplace_back(
2426 SubdomainInfo( diamond_id, subdomain_x + 1, subdomain_y + 1, subdomain_r - 1 ),
2428 -1 );
2429 }
2430 else if (
2431 boundary_vertex == BoundaryVertex::V_101 && subdomain_x < num_lateral_subdomains - 1 &&
2432 subdomain_y > 0 && subdomain_r < num_radial_subdomains - 1 )
2433 {
2434 neighborhood_vertex_[boundary_vertex].emplace_back(
2435 SubdomainInfo( diamond_id, subdomain_x + 1, subdomain_y - 1, subdomain_r + 1 ),
2437 -1 );
2438 }
2439 else if (
2440 boundary_vertex == BoundaryVertex::V_011 && subdomain_x > 0 &&
2441 subdomain_y < num_lateral_subdomains - 1 && subdomain_r < num_radial_subdomains - 1 )
2442 {
2443 neighborhood_vertex_[boundary_vertex].emplace_back(
2444 SubdomainInfo( diamond_id, subdomain_x - 1, subdomain_y + 1, subdomain_r + 1 ),
2446 -1 );
2447 }
2448 else if (
2449 boundary_vertex == BoundaryVertex::V_111 && subdomain_x < num_lateral_subdomains - 1 &&
2450 subdomain_y < num_lateral_subdomains - 1 && subdomain_r < num_radial_subdomains - 1 )
2451 {
2452 neighborhood_vertex_[boundary_vertex].emplace_back(
2453 SubdomainInfo( diamond_id, subdomain_x + 1, subdomain_y + 1, subdomain_r + 1 ),
2455 -1 );
2456 }
2457 }
2458 }
2459
2460 void setup_neighborhood_ranks(
2461 const DomainInfo& domain_info,
2462 const SubdomainToRankDistributionFunction& subdomain_to_rank )
2463 {
2464 for ( auto& neighbors : neighborhood_vertex_ | std::views::values )
2465 {
2466 for ( auto& [neighbor_subdomain_info, neighbor_boundary_vertex, neighbor_rank] : neighbors )
2467 {
2468 neighbor_rank = subdomain_to_rank(
2469 neighbor_subdomain_info,
2470 domain_info.num_subdomains_per_diamond_side(),
2471 domain_info.num_subdomains_in_radial_direction() );
2472 }
2473 }
2474
2475 for ( auto& neighbors : neighborhood_edge_ | std::views::values )
2476 {
2477 for ( auto& [neighbor_subdomain_info, neighbor_boundary_edge, _, neighbor_rank] : neighbors )
2478 {
2479 neighbor_rank = subdomain_to_rank(
2480 neighbor_subdomain_info,
2481 domain_info.num_subdomains_per_diamond_side(),
2482 domain_info.num_subdomains_in_radial_direction() );
2483 }
2484 }
2485
2486 for ( auto& [neighbor_subdomain_info, neighbor_boundary_face, _, neighbor_rank] :
2487 neighborhood_face_ | std::views::values )
2488 {
2489 neighbor_rank = subdomain_to_rank(
2490 neighbor_subdomain_info,
2491 domain_info.num_subdomains_per_diamond_side(),
2492 domain_info.num_subdomains_in_radial_direction() );
2493 }
2494 }
2495
2496 void setup_neighborhood(
2497 const DomainInfo& domain_info,
2498 const SubdomainInfo& subdomain_info,
2499 const SubdomainToRankDistributionFunction& subdomain_to_rank )
2500 {
2501 setup_neighborhood_faces( domain_info, subdomain_info );
2502 setup_neighborhood_edges( domain_info, subdomain_info );
2503 setup_neighborhood_vertices( domain_info, subdomain_info );
2504 setup_neighborhood_ranks( domain_info, subdomain_to_rank );
2505 }
2506
2507 std::map< BoundaryVertex, std::vector< NeighborSubdomainTupleVertex > > neighborhood_vertex_;
2508 std::map< BoundaryEdge, std::vector< NeighborSubdomainTupleEdge > > neighborhood_edge_;
2509 std::map< BoundaryFace, NeighborSubdomainTupleFace > neighborhood_face_;
2510};
2511
2512/// @brief Parallel data structure organizing the thick spherical shell metadata for distributed (MPI parallel)
2513/// simulations.
2514///
2515/// This is essentially a wrapper for the \ref DomainInfo and the neighborhood information (\ref SubdomainNeighborhood)
2516/// for all process-local subdomains.
2518{
2519 public:
2521
2523
2524 /// @brief Creates a \ref DistributedDomain with a single subdomain per diamond and initializes all the subdomain
2525 /// neighborhoods.
2527 const int lateral_diamond_refinement_level,
2528 const int radial_diamond_refinement_level,
2529 const real_t r_min,
2530 const real_t r_max,
2532 {
2533 return create_uniform(
2534 lateral_diamond_refinement_level,
2535 uniform_shell_radii( r_min, r_max, ( 1 << radial_diamond_refinement_level ) + 1 ),
2536 0,
2537 0,
2538 subdomain_to_rank );
2539 }
2540
2541 /// @brief Creates a \ref DistributedDomain with a single subdomain per diamond and initializes all the subdomain
2542 /// neighborhoods.
2544 const int lateral_diamond_refinement_level,
2545 const std::vector< double >& radii,
2547 {
2548 return create_uniform( lateral_diamond_refinement_level, radii, 0, 0, subdomain_to_rank );
2549 }
2550
2551 /// @brief Creates a \ref DistributedDomain with a single subdomain per diamond and initializes all the subdomain
2552 /// neighborhoods.
2554 const int lateral_diamond_refinement_level,
2555 const int radial_diamond_refinement_level,
2556 const real_t r_min,
2557 const real_t r_max,
2558 const int lateral_subdomain_refinement_level,
2559 const int radial_subdomain_refinement_level,
2561 {
2562 return create_uniform(
2563 lateral_diamond_refinement_level,
2564 uniform_shell_radii( r_min, r_max, ( 1 << radial_diamond_refinement_level ) + 1 ),
2565 lateral_subdomain_refinement_level,
2566 radial_subdomain_refinement_level,
2567 subdomain_to_rank );
2568 }
2569
2570 /// @brief Creates a \ref DistributedDomain with a single subdomain per diamond and initializes all the subdomain
2571 /// neighborhoods.
2573 const int lateral_diamond_refinement_level,
2574 const std::vector< double >& radii,
2575 const int lateral_subdomain_refinement_level,
2576 const int radial_subdomain_refinement_level,
2578 {
2579 DistributedDomain domain;
2580 domain.domain_info_ = DomainInfo(
2581 lateral_diamond_refinement_level,
2582 radii,
2583 ( 1 << lateral_subdomain_refinement_level ),
2584 ( 1 << radial_subdomain_refinement_level ) );
2585 int idx = 0;
2586 for ( const auto& subdomain : domain.domain_info_.local_subdomains( subdomain_to_rank ) )
2587 {
2588 domain.subdomains_[subdomain] = {
2589 idx, SubdomainNeighborhood( domain.domain_info_, subdomain, subdomain_to_rank ) };
2590 domain.local_subdomain_index_to_subdomain_info_[idx] = subdomain;
2591 idx++;
2592 }
2593 return domain;
2594 }
2595
2596 /// @brief MPI communicator this domain lives on. Defaults to MPI_COMM_WORLD.
2597 /// For agglomerated multigrid, coarse levels carry a sub-communicator so their
2598 /// reductions / halo exchanges hit only the participating ranks.
2599 [[nodiscard]] MPI_Comm comm() const { return comm_; }
2600
2601 /// @brief Override the MPI communicator (non-owning; caller manages lifetime).
2602 /// Used by MG setup to assign each coarse level its own sub-comm after construction.
2603 void set_comm( MPI_Comm comm ) { comm_ = comm; }
2604
2605 /// @brief Build a DistributedDomain that lives on a sub-communicator.
2606 ///
2607 /// Same geometry as create_uniform but resolves subdomain ownership against the
2608 /// caller's sub-comm rank, and stamps the sub-comm on the resulting domain so
2609 /// downstream reductions/halos run on the restricted comm.
2610 ///
2611 /// @param comm The sub-comm this domain is distributed over (MPI_COMM_NULL for
2612 /// ranks that are not part of the sub-comm at this level — the
2613 /// returned domain is then empty of local subdomains).
2614 /// @param subdomain_to_rank Must return sub-comm-local rank indices (not parent ranks).
2616 MPI_Comm comm,
2617 const int lateral_diamond_refinement_level,
2618 const std::vector< double >& radii,
2619 const int lateral_subdomain_refinement_level,
2620 const int radial_subdomain_refinement_level,
2621 const SubdomainToRankDistributionFunction& subdomain_to_rank )
2622 {
2623 DistributedDomain domain;
2624 domain.domain_info_ = DomainInfo(
2625 lateral_diamond_refinement_level,
2626 radii,
2627 ( 1 << lateral_subdomain_refinement_level ),
2628 ( 1 << radial_subdomain_refinement_level ) );
2629 domain.comm_ = comm;
2630
2631 if ( comm != MPI_COMM_NULL )
2632 {
2633 const auto my_rank = mpi::rank( comm );
2634 int idx = 0;
2635 for ( const auto& subdomain : domain.domain_info_.local_subdomains( subdomain_to_rank, my_rank ) )
2636 {
2637 domain.subdomains_[subdomain] = {
2638 idx, SubdomainNeighborhood( domain.domain_info_, subdomain, subdomain_to_rank ) };
2639 domain.local_subdomain_index_to_subdomain_info_[idx] = subdomain;
2640 idx++;
2641 }
2642 }
2643 return domain;
2644 }
2645
2646 /// @brief Returns a const reference
2647 [[nodiscard]] const DomainInfo& domain_info() const { return domain_info_; }
2648
2649 [[nodiscard]] const std::map< SubdomainInfo, std::tuple< LocalSubdomainIdx, SubdomainNeighborhood > >&
2651 {
2652 return subdomains_;
2653 }
2654
2655 [[nodiscard]] const SubdomainInfo& subdomain_info_from_local_idx( const LocalSubdomainIdx subdomain_idx ) const
2656 {
2657 return local_subdomain_index_to_subdomain_info_.at( subdomain_idx );
2658 }
2659
2660 private:
2661 DomainInfo domain_info_;
2662 std::map< SubdomainInfo, std::tuple< LocalSubdomainIdx, SubdomainNeighborhood > > subdomains_;
2663 std::map< LocalSubdomainIdx, SubdomainInfo > local_subdomain_index_to_subdomain_info_;
2664 MPI_Comm comm_ = MPI_COMM_WORLD;
2665};
2666
2668{
2670 int min;
2671 int max;
2672 double avg;
2673};
2674
2676{
2677 const auto num_local_subdomains = static_cast< int >( domain.subdomains().size() );
2678 int total = num_local_subdomains;
2679 int min = num_local_subdomains;
2680 int max = num_local_subdomains;
2681
2682 MPI_Reduce( &num_local_subdomains, &total, 1, MPI_INT, MPI_SUM, 0, domain.comm() );
2683 MPI_Reduce( &num_local_subdomains, &min, 1, MPI_INT, MPI_MIN, 0, domain.comm() );
2684 MPI_Reduce( &num_local_subdomains, &max, 1, MPI_INT, MPI_MAX, 0, domain.comm() );
2685
2686 const SubdomainDistribution result{
2687 .total = total, .min = min, .max = max, .avg = static_cast< double >( total ) / mpi::num_processes( domain.comm() ) };
2688 return result;
2689}
2690
2691template < typename ValueType >
2693 allocate_scalar_grid( const std::string label, const DistributedDomain& distributed_domain )
2694{
2696 label,
2697 distributed_domain.subdomains().size(),
2700 distributed_domain.domain_info().subdomain_num_nodes_radially() );
2701}
2702
2703template < typename ValueType >
2705 allocate_scalar_grid( const std::string label, const DistributedDomain& distributed_domain, const int level )
2706{
2708 label,
2709 distributed_domain.subdomains().size(),
2710 distributed_domain.domain_info().subdomain_num_nodes_per_side_laterally( level ),
2711 distributed_domain.domain_info().subdomain_num_nodes_per_side_laterally( level ),
2712 distributed_domain.domain_info().subdomain_num_nodes_radially( level ) );
2713}
2714
2715inline Kokkos::MDRangePolicy< Kokkos::Rank< 4 > >
2717{
2718 return Kokkos::MDRangePolicy< Kokkos::Rank< 4 > >(
2719 { 0, 0, 0, 0 },
2720 { static_cast< long long >( distributed_domain.subdomains().size() ),
2723 distributed_domain.domain_info().subdomain_num_nodes_radially() } );
2724}
2725
2726// loop only lateral dimensions of each subdomain. Used in the precomputation of lateral parts of the
2727// Jacobian (-> Oliver)
2728inline Kokkos::MDRangePolicy< Kokkos::Rank< 3 > >
2730{
2731 return Kokkos::MDRangePolicy< Kokkos::Rank< 3 > >(
2732 { 0, 0, 0 },
2733 { static_cast< long long >( distributed_domain.subdomains().size() ),
2734 distributed_domain.domain_info().subdomain_num_nodes_per_side_laterally() - 1,
2735 distributed_domain.domain_info().subdomain_num_nodes_per_side_laterally() - 1 } );
2736}
2737
2738inline Kokkos::MDRangePolicy< Kokkos::Rank< 4 > >
2740{
2741 return Kokkos::MDRangePolicy< Kokkos::Rank< 4 > >(
2742 { 0, 0, 0, 0 },
2743 { static_cast< long long >( distributed_domain.subdomains().size() ),
2744 distributed_domain.domain_info().subdomain_num_nodes_per_side_laterally() - 1,
2745 distributed_domain.domain_info().subdomain_num_nodes_per_side_laterally() - 1,
2746 distributed_domain.domain_info().subdomain_num_nodes_radially() - 1 } );
2747}
2748
2749// linearized Range instaed of MDRange to loop lateral and radial dimension,
2750// potentially yields a performance advantage.
2751inline Kokkos::RangePolicy<>
2753{
2754 return Kokkos::RangePolicy<>(
2755 0,
2756 static_cast< long long >( distributed_domain.subdomains().size() ) *
2757 ( distributed_domain.domain_info().subdomain_num_nodes_per_side_laterally() - 1 ) *
2758 ( distributed_domain.domain_info().subdomain_num_nodes_per_side_laterally() - 1 ) *
2759 ( distributed_domain.domain_info().subdomain_num_nodes_radially() - 1 ) );
2760}
2761
2762inline Kokkos::MDRangePolicy< Kokkos::Rank< 4 > >
2764{
2765 return Kokkos::MDRangePolicy< Kokkos::Rank< 4 > >(
2766 { 0, 1, 1, 1 },
2767 { static_cast< long long >( distributed_domain.subdomains().size() ),
2768 distributed_domain.domain_info().subdomain_num_nodes_per_side_laterally() + 1 - 1,
2769 distributed_domain.domain_info().subdomain_num_nodes_per_side_laterally() + 1 - 1,
2770 distributed_domain.domain_info().subdomain_num_nodes_radially() + 1 - 1 } );
2771}
2772
2773/// @brief Returns an initialized grid with the coordinates of all subdomains' nodes projected to the unit sphere.
2774///
2775/// The layout is
2776///
2777/// grid( local_subdomain_id, x_idx, y_idx, node_coord )
2778///
2779/// where node_coord is in {0, 1, 2} and refers to the cartesian coordinate of the point.
2780template < std::floating_point T >
2782{
2783 Grid3DDataVec< T, 3 > subdomain_coords(
2784 "subdomain_unit_sphere_coords",
2785 domain.subdomains().size(),
2788
2789 typename Grid3DDataVec< T, 3 >::HostMirror subdomain_coords_host = Kokkos::create_mirror_view( subdomain_coords );
2790
2791 for ( const auto& [subdomain_info, data] : domain.subdomains() )
2792 {
2793 const auto& [subdomain_idx, neighborhood] = data;
2794
2795 unit_sphere_single_shell_subdomain_coords< T >(
2796 subdomain_coords_host,
2797 subdomain_idx,
2798 subdomain_info.diamond_id(),
2801 subdomain_info.subdomain_x(),
2802 subdomain_info.subdomain_y() );
2803 }
2804
2805 Kokkos::deep_copy( subdomain_coords, subdomain_coords_host );
2806 return subdomain_coords;
2807}
2808
2809/// @brief Returns an initialized grid with the radii of all subdomain nodes.
2810///
2811/// The layout is
2812///
2813/// grid( local_subdomain_id, r_idx ) = radius
2814///
2815template < std::floating_point T >
2817{
2818 const int shells_per_subdomain = domain.domain_info().subdomain_num_nodes_radially();
2819 const int layers_per_subdomain = shells_per_subdomain - 1;
2820
2821 Grid2DDataScalar< T > radii_device( "subdomain_shell_radii", domain.subdomains().size(), shells_per_subdomain );
2822 typename Grid2DDataScalar< T >::HostMirror radii_host = Kokkos::create_mirror_view( radii_device );
2823
2824 for ( const auto& [subdomain_info, data] : domain.subdomains() )
2825 {
2826 const auto& [subdomain_idx, neighborhood] = data;
2827
2828 const int subdomain_innermost_node_idx = subdomain_info.subdomain_r() * layers_per_subdomain;
2829 const int subdomain_outermost_node_idx = subdomain_innermost_node_idx + layers_per_subdomain;
2830
2831 int j = 0;
2832 for ( int node_idx = subdomain_innermost_node_idx; node_idx <= subdomain_outermost_node_idx; node_idx++ )
2833 {
2834 radii_host( subdomain_idx, j ) = domain.domain_info().radii()[node_idx];
2835 j++;
2836 }
2837 }
2838
2839 Kokkos::deep_copy( radii_device, radii_host );
2840 return radii_device;
2841}
2842
2843/// @brief Returns an initialized grid with the shell index of all subdomain nodes.
2844///
2845/// The layout is
2846///
2847/// grid( local_subdomain_id, r_idx ) = global_shell_idx
2848///
2850{
2851 const int shells_per_subdomain = domain.domain_info().subdomain_num_nodes_radially();
2852 const int layers_per_subdomain = shells_per_subdomain - 1;
2853
2854 Grid2DDataScalar< int > shell_idx_device( "subdomain_shell_idx", domain.subdomains().size(), shells_per_subdomain );
2855 Grid2DDataScalar< int >::HostMirror shell_idx_host = Kokkos::create_mirror_view( shell_idx_device );
2856
2857 for ( const auto& [subdomain_info, data] : domain.subdomains() )
2858 {
2859 const auto& [subdomain_idx, neighborhood] = data;
2860 const int subdomain_innermost_node_idx = subdomain_info.subdomain_r() * layers_per_subdomain;
2861 for ( int j = 0; j < shells_per_subdomain; j++ )
2862 {
2863 shell_idx_host( subdomain_idx, j ) = subdomain_innermost_node_idx + j;
2864 }
2865 }
2866 Kokkos::deep_copy( shell_idx_device, shell_idx_host );
2867 return shell_idx_device;
2868}
2869
2870template < typename CoordsShellType, typename CoordsRadiiType >
2872 const int subdomain,
2873 const int x,
2874 const int y,
2875 const int r,
2876 const CoordsShellType& coords_shell,
2877 const CoordsRadiiType& coords_radii )
2878{
2879 using T = CoordsShellType::value_type;
2880 static_assert( std::is_same_v< T, typename CoordsRadiiType::value_type > );
2881
2882 static_assert(
2883 std::is_same_v< CoordsShellType, Grid3DDataVec< T, 3 > > ||
2884 std::is_same_v< CoordsShellType, typename Grid3DDataVec< T, 3 >::HostMirror > );
2885
2886 static_assert(
2887 std::is_same_v< CoordsRadiiType, Grid2DDataScalar< T > > ||
2888 std::is_same_v< CoordsRadiiType, typename Grid2DDataScalar< T >::HostMirror > );
2889
2891 coords( 0 ) = coords_shell( subdomain, x, y, 0 );
2892 coords( 1 ) = coords_shell( subdomain, x, y, 1 );
2893 coords( 2 ) = coords_shell( subdomain, x, y, 2 );
2894 return coords * coords_radii( subdomain, r );
2895}
2896
2897template < typename CoordsShellType, typename CoordsRadiiType >
2899 const dense::Vec< int, 4 > subdomain_x_y_r,
2900 const CoordsShellType& coords_shell,
2901 const CoordsRadiiType& coords_radii )
2902{
2903 using T = CoordsShellType::value_type;
2904 static_assert( std::is_same_v< T, typename CoordsRadiiType::value_type > );
2905
2906 static_assert(
2907 std::is_same_v< CoordsShellType, Grid3DDataVec< T, 3 > > ||
2908 std::is_same_v< CoordsShellType, typename Grid3DDataVec< T, 3 >::HostMirror > );
2909
2910 static_assert(
2911 std::is_same_v< CoordsRadiiType, Grid2DDataScalar< T > > ||
2912 std::is_same_v< CoordsRadiiType, typename Grid2DDataScalar< T >::HostMirror > );
2913
2914 return coords(
2915 subdomain_x_y_r( 0 ),
2916 subdomain_x_y_r( 1 ),
2917 subdomain_x_y_r( 2 ),
2918 subdomain_x_y_r( 3 ),
2919 coords_shell,
2920 coords_radii );
2921}
2922
2923} // namespace terra::grid::shell
Parallel data structure organizing the thick spherical shell metadata for distributed (MPI parallel) ...
Definition spherical_shell.hpp:2518
const std::map< SubdomainInfo, std::tuple< LocalSubdomainIdx, SubdomainNeighborhood > > & subdomains() const
Definition spherical_shell.hpp:2650
const SubdomainInfo & subdomain_info_from_local_idx(const LocalSubdomainIdx subdomain_idx) const
Definition spherical_shell.hpp:2655
int LocalSubdomainIdx
Definition spherical_shell.hpp:2522
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:2543
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:2572
const DomainInfo & domain_info() const
Returns a const reference.
Definition spherical_shell.hpp:2647
MPI_Comm comm() const
MPI communicator this domain lives on. Defaults to MPI_COMM_WORLD. For agglomerated multigrid,...
Definition spherical_shell.hpp:2599
static DistributedDomain create_uniform_on_comm(MPI_Comm comm, 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)
Build a DistributedDomain that lives on a sub-communicator.
Definition spherical_shell.hpp:2615
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:2526
void set_comm(MPI_Comm comm)
Override the MPI communicator (non-owning; caller manages lifetime). Used by MG setup to assign each ...
Definition spherical_shell.hpp:2603
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:2553
Information about the thick spherical shell mesh.
Definition spherical_shell.hpp:780
long long num_micro_hex_cells_per_subdomain() const
Number of hex cells on the finest level, per subdomain.
Definition spherical_shell.hpp:955
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
int num_global_subdomains() const
Number of global subdomains.
Definition spherical_shell.hpp:948
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, mpi::MPIRank my_rank) const
Same as the no-arg overload but compares subdomain owners against an explicit rank.
Definition spherical_shell.hpp:919
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
long long num_global_micro_hex_cells() const
Number of hex cells on the finest level, globally.
Definition spherical_shell.hpp:962
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:1057
SubdomainNeighborhood(const DomainInfo &domain_info, const SubdomainInfo &subdomain_info, const SubdomainToRankDistributionFunction &subdomain_to_rank)
Definition spherical_shell.hpp:1068
const std::map< BoundaryFace, NeighborSubdomainTupleFace > & neighborhood_face() const
Definition spherical_shell.hpp:1080
std::tuple< SubdomainInfo, BoundaryEdge, UnpackOrderingEdge, mpi::MPIRank > NeighborSubdomainTupleEdge
Definition spherical_shell.hpp:1063
std::tuple< SubdomainInfo, BoundaryFace, UnpackOrderingFace, mpi::MPIRank > NeighborSubdomainTupleFace
Definition spherical_shell.hpp:1064
const std::map< BoundaryEdge, std::vector< NeighborSubdomainTupleEdge > > & neighborhood_edge() const
Definition spherical_shell.hpp:1077
std::tuple< BoundaryDirection, BoundaryDirection > UnpackOrderingFace
Definition spherical_shell.hpp:1060
std::tuple< SubdomainInfo, BoundaryVertex, mpi::MPIRank > NeighborSubdomainTupleVertex
Definition spherical_shell.hpp:1062
const std::map< BoundaryVertex, std::vector< NeighborSubdomainTupleVertex > > & neighborhood_vertex() const
Definition spherical_shell.hpp:1074
Definition agglomerated_distribution.hpp:7
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:2693
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:2781
Grid2DDataScalar< T > subdomain_shell_radii(const DistributedDomain &domain)
Returns an initialized grid with the radii of all subdomain nodes.
Definition spherical_shell.hpp:2816
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:2871
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:2675
Grid2DDataScalar< int > subdomain_shell_idx(const DistributedDomain &domain)
Returns an initialized grid with the shell index of all subdomain nodes.
Definition spherical_shell.hpp:2849
Kokkos::MDRangePolicy< Kokkos::Rank< 4 > > local_domain_md_range_policy_cells(const DistributedDomain &distributed_domain)
Definition spherical_shell.hpp:2739
Kokkos::MDRangePolicy< Kokkos::Rank< 3 > > local_domain_md_range_policy_cells_lateral(const DistributedDomain &distributed_domain)
Definition spherical_shell.hpp:2729
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:2716
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:2752
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::MDRangePolicy< Kokkos::Rank< 4 > > local_domain_md_range_policy_cells_fv_skip_ghost_layers(const DistributedDomain &distributed_domain)
Definition spherical_shell.hpp:2763
Kokkos::View< ScalarType ***[VecDim], Layout > Grid3DDataVec
Definition grid_types.hpp:42
constexpr std::array all_boundary_faces
Definition grid_types.hpp:393
BoundaryDirection
Enum for the iteration direction at a boundary.
Definition grid_types.hpp:311
constexpr std::array all_boundary_vertices
Definition grid_types.hpp:366
constexpr std::array all_boundary_edges
Definition grid_types.hpp:376
Kokkos::View< ScalarType ****, Layout > Grid4DDataScalar
Definition grid_types.hpp:27
Kokkos::View< ScalarType **, Layout > Grid2DDataScalar
Definition grid_types.hpp:21
int num_processes()
Definition mpi.hpp:27
int MPIRank
Definition mpi.hpp:11
MPIRank rank()
Definition mpi.hpp:13
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:2668
int min
Definition spherical_shell.hpp:2670
int max
Definition spherical_shell.hpp:2671
int total
Definition spherical_shell.hpp:2669
double avg
Definition spherical_shell.hpp:2672