feectools 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. feectools/__init__.py +0 -0
  2. feectools/accelerate/__init__.py +0 -0
  3. feectools/accelerate/accelerate.py +220 -0
  4. feectools/accelerate/compile_psydac.mk +52 -0
  5. feectools/api/__init__.py +0 -0
  6. feectools/api/essential_bc.py +122 -0
  7. feectools/api/fem_bilinear_form.py +2226 -0
  8. feectools/api/fem_common.py +286 -0
  9. feectools/api/fem_sum_form.py +123 -0
  10. feectools/api/settings.py +82 -0
  11. feectools/core/__init__.py +11 -0
  12. feectools/core/bsplines.py +1107 -0
  13. feectools/core/bsplines_kernels.py +1349 -0
  14. feectools/core/field_evaluation_kernels.py +5015 -0
  15. feectools/core/tests/__init__.py +0 -0
  16. feectools/core/tests/test_bsplines.py +263 -0
  17. feectools/core/tests/test_bsplines_kernel.py +40 -0
  18. feectools/core/tests/test_bsplines_pyccel.py +752 -0
  19. feectools/ddm/__init__.py +3 -0
  20. feectools/ddm/basic.py +78 -0
  21. feectools/ddm/blocking_data_exchanger.py +348 -0
  22. feectools/ddm/cart.py +1835 -0
  23. feectools/ddm/interface_data_exchanger.py +122 -0
  24. feectools/ddm/mpi.py +109 -0
  25. feectools/ddm/nonblocking_data_exchanger.py +331 -0
  26. feectools/ddm/partition.py +207 -0
  27. feectools/ddm/petsc.py +112 -0
  28. feectools/ddm/tests/__init__.py +0 -0
  29. feectools/ddm/tests/test_cart_1d.py +138 -0
  30. feectools/ddm/tests/test_cart_2d.py +164 -0
  31. feectools/ddm/tests/test_cart_3d.py +158 -0
  32. feectools/ddm/tests/test_multicart_2d.py +173 -0
  33. feectools/ddm/tests/test_partition.py +124 -0
  34. feectools/ddm/utilities.py +24 -0
  35. feectools/feec/__init__.py +0 -0
  36. feectools/feec/derivatives.py +780 -0
  37. feectools/feec/dof_kernels.py +210 -0
  38. feectools/feec/global_geometric_projectors.py +1073 -0
  39. feectools/feec/hodge.py +148 -0
  40. feectools/fem/__init__.py +0 -0
  41. feectools/fem/basic.py +465 -0
  42. feectools/fem/grid.py +181 -0
  43. feectools/fem/partitioning.py +344 -0
  44. feectools/fem/projectors.py +160 -0
  45. feectools/fem/splines.py +559 -0
  46. feectools/fem/tensor.py +1393 -0
  47. feectools/fem/tests/__init__.py +0 -0
  48. feectools/fem/tests/analytical_profiles_1d.py +100 -0
  49. feectools/fem/tests/analytical_profiles_base.py +34 -0
  50. feectools/fem/tests/splines_error_bounds.py +155 -0
  51. feectools/fem/tests/test_spline_histopolation.py +120 -0
  52. feectools/fem/tests/test_spline_interpolation.py +182 -0
  53. feectools/fem/tests/test_splines.py +184 -0
  54. feectools/fem/tests/test_splines_par.py +46 -0
  55. feectools/fem/tests/test_vector_spaces.py +150 -0
  56. feectools/fem/tests/utilities.py +47 -0
  57. feectools/fem/vector.py +729 -0
  58. feectools/linalg/__init__.py +0 -0
  59. feectools/linalg/basic.py +1386 -0
  60. feectools/linalg/block.py +1451 -0
  61. feectools/linalg/direct_solvers.py +201 -0
  62. feectools/linalg/fft.py +258 -0
  63. feectools/linalg/kernels/__init__.py +0 -0
  64. feectools/linalg/kernels/axpy_kernels.py +57 -0
  65. feectools/linalg/kernels/inner_kernels.py +100 -0
  66. feectools/linalg/kernels/matvec_kernels.py +206 -0
  67. feectools/linalg/kernels/stencil2IJV_kernels.py +227 -0
  68. feectools/linalg/kernels/stencil2coo_kernels.py +179 -0
  69. feectools/linalg/kernels/transpose_kernels.py +263 -0
  70. feectools/linalg/kron.py +911 -0
  71. feectools/linalg/solvers.py +1914 -0
  72. feectools/linalg/sparse.py +114 -0
  73. feectools/linalg/stencil.py +2923 -0
  74. feectools/linalg/stencil_dot_kernels.py +317 -0
  75. feectools/linalg/stencil_transpose_kernels.py +372 -0
  76. feectools/linalg/tests/__init__.py +0 -0
  77. feectools/linalg/tests/test_block.py +1588 -0
  78. feectools/linalg/tests/test_fft.py +106 -0
  79. feectools/linalg/tests/test_kron_stencil_matrix.py +114 -0
  80. feectools/linalg/tests/test_linalg.py +1065 -0
  81. feectools/linalg/tests/test_matrix_free.py +128 -0
  82. feectools/linalg/tests/test_solvers.py +213 -0
  83. feectools/linalg/tests/test_stencil_interface_matrix.py +379 -0
  84. feectools/linalg/tests/test_stencil_vector.py +1036 -0
  85. feectools/linalg/tests/test_stencil_vector_space.py +440 -0
  86. feectools/linalg/topetsc.py +522 -0
  87. feectools/linalg/utilities.py +200 -0
  88. feectools/utilities/__init__.py +0 -0
  89. feectools/utilities/quadratures.py +113 -0
  90. feectools/utilities/utils.py +166 -0
  91. feectools/version.py +1 -0
  92. feectools-0.1.0.dist-info/METADATA +66 -0
  93. feectools-0.1.0.dist-info/RECORD +98 -0
  94. feectools-0.1.0.dist-info/WHEEL +5 -0
  95. feectools-0.1.0.dist-info/entry_points.txt +3 -0
  96. feectools-0.1.0.dist-info/licenses/AUTHORS +22 -0
  97. feectools-0.1.0.dist-info/licenses/LICENSE +21 -0
  98. feectools-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,207 @@
1
+ import numpy as np
2
+ import numpy.ma as ma
3
+
4
+ from sympy.ntheory import factorint
5
+
6
+ __all__ = ('compute_dims', 'partition_procs_per_patch')
7
+
8
+ #==============================================================================
9
+ def partition_procs_per_patch(npts, size):
10
+ """
11
+ Compute the number of processes in each patch and assign to it an ascending range of processes.
12
+ The processes are distributed porportionally to the patch grid size.
13
+
14
+ Parameters
15
+ ----------
16
+ npts : list
17
+ Number of points along each dimension for each patch.
18
+
19
+ size : int
20
+ Number of processes.
21
+
22
+ Returns
23
+ -------
24
+ sizes : list of int
25
+ Number of processes in each patch.
26
+
27
+ ranges: list of list of int
28
+ The assigned ascending range of processes for each patch,
29
+ the range is represented by a list of ints of size 2 [k1,k2],
30
+ such that k1<=k2.
31
+
32
+ """
33
+ npts = [np.prod(nc) for nc in npts]
34
+ percentage = [nc / sum(npts) for nc in npts]
35
+ sizes = np.array([int(p*size) for p in percentage])
36
+ diff = [p * size - s for s, p in zip(sizes, percentage)]
37
+ indices = np.argsort(diff)[::-1]
38
+ rm = size - sum(sizes)
39
+
40
+ sizes[indices[:rm]] +=1
41
+ assert sum(sizes) == size
42
+
43
+ #...
44
+ start = 0
45
+ ranges = []
46
+ for s in sizes:
47
+ ranges.append([start, start+s-1])
48
+ start += s
49
+
50
+ assert start == size
51
+
52
+ ranges = np.array(ranges)
53
+ ranks = [i[0] for i in ranges[indices[:rm]]]
54
+
55
+ if len(ranks) == 0:
56
+ if any(s==0 for s in sizes):
57
+ raise ValueError("Cannot compute sizes with given input values!")
58
+
59
+ k = 0
60
+ for i,s in enumerate(sizes):
61
+ if s > 0:
62
+ continue
63
+ sizes[i] = 1
64
+ ranges[i] = [ranks[k], ranks[k]]
65
+ k = (k+1) % size
66
+
67
+ return sizes, ranges
68
+
69
+ #==============================================================================
70
+ def compute_dims( nnodes, gridsizes, min_blocksizes=None, mpi=None, try_uniform=False, mpi_dims_mask=None ):
71
+ """
72
+ With the aim of distributing a multi-dimensional array on a Cartesian
73
+ topology, compute the number of processes along each dimension.
74
+
75
+ Whenever possible, the number of processes is chosen so that the array is
76
+ decomposed into identical blocks.
77
+
78
+ Parameters
79
+ ----------
80
+ nnodes : int
81
+ Number of processes in the Cartesian topology.
82
+
83
+ gridsizes : list of int
84
+ Number of array elements along each dimension.
85
+
86
+ min_blocksizes : list of int
87
+ Minimum acceptable size of a block along each dimension.
88
+
89
+ try_uniform: bool
90
+ try to decompose the array uniformly.
91
+
92
+ mpi_dims_mask: list of bool
93
+ True if the dimension is to be used in the domain decomposition (=default for each dimension).
94
+ If dim_mask[i]=False, the domain decomposition yields blocksizes[i]=gridsizes[i] along the i-th dimension.
95
+
96
+ Returns
97
+ -------
98
+ dims : list of int
99
+ Number of processes along each dimension of the Cartesian topology.
100
+
101
+ blocksizes : list of int
102
+ Nominal block size along each dimension.
103
+
104
+ """
105
+ assert nnodes > 0
106
+ assert all( s > 0 for s in gridsizes )
107
+ assert np.prod( gridsizes ) >= nnodes
108
+
109
+ if (min_blocksizes is not None):
110
+ assert len( min_blocksizes ) == len( gridsizes )
111
+ assert all( m > 0 for m in gridsizes )
112
+ assert all( s >= m for s,m in zip( gridsizes, min_blocksizes ) )
113
+
114
+ # Determine whether uniform decomposition is possible
115
+ uniform = (np.prod( gridsizes ) % nnodes == 0)
116
+
117
+ # Compute dimensions of MPI Cartesian topology with most appropriate algorithm
118
+ if try_uniform and uniform:
119
+ dims, blocksizes = compute_dims_uniform( nnodes, gridsizes )
120
+ else:
121
+ dims, blocksizes = compute_dims_general( nnodes, gridsizes, mpi_dims_mask=mpi_dims_mask )
122
+
123
+ # If a minimum block size is given, verify that condition is met
124
+
125
+ if min_blocksizes is not None:
126
+ too_small = any( [s < m for (s,m) in zip( blocksizes, min_blocksizes )] )
127
+
128
+ # If uniform decomposition yields blocks too small, fall back to general algorithm
129
+ if uniform and too_small:
130
+ dims, blocksizes = compute_dims_general( nnodes, gridsizes )
131
+ too_small = any( [s < m for (s,m) in zip( blocksizes, min_blocksizes )] )
132
+
133
+ # If general decomposition yields blocks too small, raise error
134
+ if too_small:
135
+ raise ValueError("Cannot compute dimensions with the minimum acceptable block sizes {}".format(tuple(min_blocksizes)))
136
+
137
+ return dims, blocksizes
138
+
139
+ #==============================================================================
140
+ def compute_dims_general( mpi_size, npts, mpi_dims_mask=None ):
141
+
142
+ if mpi_dims_mask is None:
143
+ mpi_dims_mask = [True] * len(npts)
144
+ else:
145
+ assert len(mpi_dims_mask) == len(npts), "mpi_dims_mask must have one entry for each dimension."
146
+ assert all(isinstance(m, bool) for m in mpi_dims_mask), "mpi_dims_mask must only contain True/False values."
147
+ assert any(mpi_dims_mask), "mpi_dims_mask must contain at least one True value."
148
+
149
+ nprocs = [1]*len( npts )
150
+
151
+ shape = []
152
+ for n, use_dim in zip(npts, mpi_dims_mask):
153
+ if use_dim:
154
+ shape += [n]
155
+ else:
156
+ shape += [-1]
157
+
158
+ f = factorint( mpi_size, multiple=True )
159
+ f.sort( reverse=True )
160
+
161
+ for a in f:
162
+
163
+ i = np.argmax( shape )
164
+ max_shape = shape[i]
165
+
166
+ if shape.count( max_shape ) > 1:
167
+ i = ma.array( nprocs, mask=np.not_equal( shape, max_shape ) ).argmin()
168
+
169
+ nprocs[i] *= a
170
+ shape [i] //= a
171
+
172
+ for i, use_dim in enumerate(mpi_dims_mask):
173
+ if not use_dim:
174
+ shape[i] = npts[i]
175
+
176
+ return nprocs, shape
177
+
178
+ #==============================================================================
179
+ def compute_dims_uniform( mpi_size, npts ):
180
+
181
+ nprocs = [1]*len( npts )
182
+
183
+ mpi_factors = factorint( int(mpi_size) )
184
+ npts_factors = [factorint( int(n) ) for n in npts]
185
+
186
+ nprocs = [1 for n in npts]
187
+
188
+ for a,power in mpi_factors.items():
189
+
190
+ exponents = [f.get( a, 0 ) for f in npts_factors]
191
+
192
+ for k in range( power ):
193
+
194
+ i = np.argmax( exponents )
195
+ max_exp = exponents[i]
196
+
197
+ if exponents.count( max_exp ) > 1:
198
+ i = ma.array( nprocs, mask=np.not_equal( exponents, max_exp ) ).argmin()
199
+
200
+ nprocs [i] *= a
201
+ exponents[i] -= 1
202
+
203
+ npts_factors[i][a] -= 1
204
+
205
+ shape = [np.prod( [key**val for key,val in f.items()] ) for f in npts_factors]
206
+
207
+ return nprocs, shape
feectools/ddm/petsc.py ADDED
@@ -0,0 +1,112 @@
1
+ # coding: utf-8
2
+
3
+ import numpy as np
4
+ from itertools import product
5
+
6
+ from .cart import CartDecomposition
7
+
8
+ #===============================================================================
9
+ class PetscCart:
10
+
11
+ def __init__(self, cart):
12
+ assert isinstance(cart, CartDecomposition)
13
+
14
+ try:
15
+ from petsc4py import PETSc
16
+ except ImportError:
17
+ raise ImportError('petsc4py needs to be installed in order to use the class PetscCart')
18
+
19
+ self._cart = cart
20
+ self._petsc = PETSc
21
+
22
+ self._indices = self._create_indices()
23
+ self._extended_indices = self._create_extended_indices()
24
+ self._ao = self._create_Ao()
25
+ self._l2g_mapping = self._create_LGMap()
26
+
27
+ # Compute local shape of local arrays in topology (without ghost regions)
28
+ self._local_shape = tuple( e-s+1 for s,e in zip( cart._starts, cart._ends ) )
29
+
30
+ # Compute local size of local arrays in topology (without ghost regions)
31
+ self._local_size = np.prod(self._local_shape)
32
+
33
+
34
+ @property
35
+ def cart( self ):
36
+ return self._cart
37
+
38
+ @property
39
+ def petsc( self ):
40
+ return self._petsc
41
+
42
+ @property
43
+ def indices( self ):
44
+ return self._indices
45
+
46
+ @property
47
+ def extended_indices( self ):
48
+ return self._extended_indices
49
+
50
+ @property
51
+ def ao( self ):
52
+ return self._ao
53
+
54
+ @property
55
+ def local_size( self ):
56
+ return self._local_size
57
+
58
+ @property
59
+ def local_shape( self ):
60
+ return self._local_shape
61
+
62
+ @property
63
+ def l2g_mapping( self ):
64
+ return self._l2g_mapping
65
+
66
+ def _create_indices( self ):
67
+ """ Create the global indices without the ghost regions.
68
+ """
69
+ cart = self.cart
70
+ indices = product(*cart._grids)
71
+ npts = cart.npts
72
+ array = [np.ravel_multi_index(i, npts) for i in indices]
73
+ return array
74
+
75
+ def _create_extended_indices( self ):
76
+ """ Create the global indices with the ghost regions.
77
+ """
78
+ cart = self.cart
79
+ indices = product(*cart._extended_grids)
80
+ npts = cart.npts
81
+ mode = tuple('wrap' if P else 'clip' for P in cart.periods)
82
+ array = [np.ravel_multi_index(i, npts, mode=mode) for i in indices]
83
+ return array
84
+
85
+ def _create_Ao( self ):
86
+ """ Create the mapping between the global ordering and the natural ordering.
87
+ """
88
+ cart = self.cart
89
+ indices = self.indices
90
+ return self.petsc.AO().createBasic(indices, comm=cart.comm)
91
+
92
+ def _create_LGMap( self ):
93
+ """ Create local to global mapping.
94
+ """
95
+ cart = self.cart
96
+ indices = self.extended_indices
97
+ ao = self.ao
98
+ return self.petsc.LGMap().create(ao.app2petsc(indices), comm=cart.comm)
99
+
100
+ def create_g2n(self, gvec, natural):
101
+ """ This method creates a natural ordering vector from a global ordering vector.
102
+ """
103
+ cart = self.cart
104
+ indices = self.indices
105
+ size = self.local_size
106
+ start,_ = natural.getOwnershipRange()
107
+
108
+ from_is = self.petsc.IS().createStride(size, start, 1, comm=cart.comm)
109
+ to_is = self.petsc.IS().createGeneral(indices, comm=cart.comm)
110
+
111
+ return self.petsc.Scatter().create(gvec, from_is, natural, to_is)
112
+
File without changes
@@ -0,0 +1,138 @@
1
+ # Contents of test_cart_1d.py
2
+
3
+ import numpy as np
4
+
5
+ from feectools.ddm.blocking_data_exchanger import BlockingCartDataExchanger
6
+ from feectools.ddm.nonblocking_data_exchanger import NonBlockingCartDataExchanger
7
+
8
+ #===============================================================================
9
+ # TEST CartDecomposition and CartDataExchanger in 1D
10
+ #===============================================================================
11
+ def run_cart_1d( data_exchanger_type, verbose=False ):
12
+
13
+ import numpy as np
14
+ from feectools.ddm.mpi import mpi as MPI
15
+ from feectools.ddm.cart import DomainDecomposition, CartDecomposition
16
+
17
+ #---------------------------------------------------------------------------
18
+ # INPUT PARAMETERS
19
+ #---------------------------------------------------------------------------
20
+
21
+ # Number of cells
22
+ nc1 = 135
23
+
24
+ # Padding ('thickness' of ghost region)
25
+ p1 = 3
26
+
27
+ # Periodicity
28
+ period1 = True
29
+
30
+ # Number of Points
31
+ n1 = nc1 + p1*(1-period1)
32
+ #---------------------------------------------------------------------------
33
+ # DOMAIN DECOMPOSITION
34
+ #---------------------------------------------------------------------------
35
+
36
+ # Parallel info
37
+ comm = MPI.COMM_WORLD
38
+ size = comm.Get_size()
39
+ rank = comm.Get_rank()
40
+
41
+ domain_decomposition = DomainDecomposition(ncells=[nc1], periods=[period1], comm=comm)
42
+
43
+ es = domain_decomposition.global_element_starts[0]
44
+ ee = domain_decomposition.global_element_ends [0]
45
+
46
+ global_ends = [ee]
47
+ global_ends[0][-1] = n1-1
48
+ global_starts = [np.array([0] + (global_ends[0][:-1]+1).tolist())]
49
+
50
+ # Decomposition of Cartesian domain
51
+ cart = CartDecomposition(
52
+ domain_decomposition = domain_decomposition,
53
+ npts = [n1],
54
+ global_starts = global_starts,
55
+ global_ends = global_ends,
56
+ pads = [p1],
57
+ shifts = [1],
58
+ )
59
+
60
+ # Local 1D array (extended domain)
61
+ u = np.zeros( cart.shape, dtype=int )
62
+
63
+ # Global indices of first and last elements of array
64
+ s1, = cart.starts
65
+ e1, = cart.ends
66
+
67
+ # Create object in charge of exchanging data between subdomains
68
+ synchronizer = data_exchanger_type( cart, u.dtype )
69
+
70
+ # Print some info
71
+ if verbose:
72
+
73
+ if rank == 0:
74
+ print( "" )
75
+
76
+ for k in range(size):
77
+ if k == rank:
78
+ print( "RANK = {}".format( rank ) )
79
+ print( "---------" )
80
+ print( ". s1:e1 = {:2d}:{:2d}".format( s1,e1 ) )
81
+ print( "", flush=True )
82
+ comm.Barrier()
83
+
84
+ #---------------------------------------------------------------------------
85
+ # TEST
86
+ #---------------------------------------------------------------------------
87
+
88
+ # Fill in true domain with u[i1_loc]=i1_glob
89
+ u[p1:-p1] = [i1 for i1 in range(s1,e1+1)]
90
+
91
+ request = synchronizer.prepare_communications(u)
92
+
93
+ # Update ghost regions
94
+ synchronizer.start_update_ghost_regions( u, request )
95
+ synchronizer.end_update_ghost_regions( u, request )
96
+
97
+ #---------------------------------------------------------------------------
98
+ # CHECK RESULTS
99
+ #---------------------------------------------------------------------------
100
+ # Verify that ghost cells contain correct data (note periodic domain!)
101
+ success = all( u[:] == [i1%n1 for i1 in range(s1-p1,e1+p1+1)] )
102
+
103
+ # MASTER only: collect information from all processes
104
+ success_global = comm.reduce( success, op=MPI.LAND, root=0 )
105
+
106
+ return locals()
107
+
108
+ #===============================================================================
109
+ # RUN TEST WITH PYTEST
110
+ #===============================================================================
111
+ import pytest
112
+
113
+ @pytest.mark.parametrize( 'data_exchanger_type', [BlockingCartDataExchanger, NonBlockingCartDataExchanger] )
114
+ @pytest.mark.parallel
115
+ def test_cart_1d( data_exchanger_type ):
116
+
117
+ namespace = run_cart_1d( data_exchanger_type )
118
+
119
+ assert namespace['success']
120
+
121
+ #===============================================================================
122
+ # RUN TEST MANUALLY
123
+ #===============================================================================
124
+ if __name__=='__main__':
125
+
126
+ locals().update( run_cart_1d( BlockingCartDataExchanger, verbose=True ) )
127
+
128
+ # Print error messages (if any) in orderly fashion
129
+ for k in range(size):
130
+ if k == rank and not success:
131
+ print( "Rank {}: wrong ghost cell data!".format( rank ), flush=True )
132
+ comm.Barrier()
133
+
134
+ if rank == 0:
135
+ if success_global:
136
+ print( "PASSED", end='\n\n', flush=True )
137
+ else:
138
+ print( "FAILED", end='\n\n', flush=True )
@@ -0,0 +1,164 @@
1
+ # File test_cart_2d.py
2
+
3
+ from feectools.ddm.blocking_data_exchanger import BlockingCartDataExchanger
4
+ from feectools.ddm.nonblocking_data_exchanger import NonBlockingCartDataExchanger
5
+
6
+ #===============================================================================
7
+ # TEST CartDecomposition and CartDataExchanger in 2D
8
+ #===============================================================================
9
+ def run_cart_2d( data_exchanger_type, verbose=False , nprocs=None, reverse_axis=None):
10
+
11
+ import numpy as np
12
+ from feectools.ddm.mpi import mpi as MPI
13
+ from feectools.ddm.cart import DomainDecomposition, CartDecomposition
14
+
15
+ #---------------------------------------------------------------------------
16
+ # INPUT PARAMETERS
17
+ #---------------------------------------------------------------------------
18
+
19
+ # Number of cells
20
+ nc1 = 135
21
+ nc2 = 77
22
+
23
+ # Padding ('thickness' of ghost region)
24
+ p1 = 3
25
+ p2 = 2
26
+
27
+ # Periodicity
28
+ period1 = True
29
+ period2 = False
30
+
31
+ # Number of Points
32
+ n1 = nc1 + p1*(1-period1)
33
+ n2 = nc2 + p2*(1-period2)
34
+ #---------------------------------------------------------------------------
35
+ # DOMAIN DECOMPOSITION
36
+ #---------------------------------------------------------------------------
37
+
38
+ # Parallel info
39
+ comm = MPI.COMM_WORLD
40
+ size = comm.Get_size()
41
+ rank = comm.Get_rank()
42
+
43
+ domain_decomposition = DomainDecomposition(ncells=[nc1,nc2], periods=[period1,period2], comm=comm)
44
+
45
+ npts = [n1,n2]
46
+ global_starts = [None]*2
47
+ global_ends = [None]*2
48
+ for axis in range(2):
49
+ es = domain_decomposition.global_element_starts[axis]
50
+ ee = domain_decomposition.global_element_ends [axis]
51
+
52
+ global_ends [axis] = (ee+1)-1
53
+ global_ends [axis][-1] = npts[axis]-1
54
+ global_starts[axis] = np.array([0] + (global_ends[axis][:-1]+1).tolist())
55
+
56
+ # Decomposition of Cartesian domain
57
+ cart = CartDecomposition(
58
+ domain_decomposition = domain_decomposition,
59
+ npts = [n1,n2],
60
+ global_starts = global_starts,
61
+ global_ends = global_ends,
62
+ pads = [p1,p2],
63
+ shifts = [1,1],
64
+ )
65
+
66
+ # Local 2D array with 2D vector data (extended domain)
67
+ shape = list( cart.shape ) + [2]
68
+ u = np.zeros( shape, dtype=int )
69
+
70
+ # Global indices of first and last elements of array
71
+ s1,s2 = cart.starts
72
+ e1,e2 = cart.ends
73
+
74
+ # Create object in charge of exchanging data between subdomains
75
+ synchronizer = data_exchanger_type( cart, u.dtype, coeff_shape=[2] )
76
+
77
+ # Print some info
78
+ if rank == 0:
79
+ print( "" )
80
+
81
+ for k in range(size):
82
+ if k == rank:
83
+ print( "Proc. # {}".format( rank ) )
84
+ print( "---------" )
85
+ print( ". s1:e1 = {:2d}:{:2d}".format( s1,e1 ) )
86
+ print( ". s2:e2 = {:2d}:{:2d}".format( s2,e2 ) )
87
+ print( "", flush=True )
88
+ comm.Barrier()
89
+
90
+ #---------------------------------------------------------------------------
91
+ # TEST
92
+ #---------------------------------------------------------------------------
93
+
94
+ # Fill in true domain with u[i1_loc,i2_loc,:]=[i1_glob,i2_glob]
95
+ u[p1:-p1,p2:-p2,:] = [[(i1,i2) for i2 in range(s2,e2+1)] for i1 in range(s1,e1+1)]
96
+
97
+
98
+ request = synchronizer.prepare_communications(u)
99
+
100
+ # Update ghost regions
101
+ synchronizer.start_update_ghost_regions( u, request )
102
+ synchronizer.end_update_ghost_regions( u, request )
103
+
104
+ #---------------------------------------------------------------------------
105
+ # CHECK RESULTS
106
+ #---------------------------------------------------------------------------
107
+
108
+ # Verify that ghost cells contain correct data (note periodic domain!)
109
+ val = lambda i1,i2: (i1%n1,i2) if 0<=i2<n2 else (0,0)
110
+ uex = [[val(i1,i2) for i2 in range(s2-p2,e2+p2+1)] for i1 in range(s1-p1,e1+p1+1)]
111
+
112
+ success = (u == uex).all()
113
+
114
+ # MASTER only: collect information from all processes
115
+ success_global = comm.reduce( success, op=MPI.LAND, root=0 )
116
+
117
+ return locals()
118
+
119
+ #===============================================================================
120
+ # RUN TEST WITH PYTEST
121
+ #===============================================================================
122
+ import pytest
123
+
124
+ @pytest.mark.parametrize( 'data_exchanger_type', [BlockingCartDataExchanger, NonBlockingCartDataExchanger] )
125
+ @pytest.mark.parallel
126
+ def test_cart_2d(data_exchanger_type):
127
+
128
+ namespace = run_cart_2d(data_exchanger_type)
129
+
130
+ assert namespace['success']
131
+
132
+ @pytest.mark.parallel
133
+ def test_cart_2d_reverse_axis_0():
134
+
135
+ namespace = run_cart_2d(BlockingCartDataExchanger, reverse_axis=0)
136
+
137
+ assert namespace['success']
138
+
139
+ @pytest.mark.parallel
140
+ def test_cart_2d_reverse_axis_1():
141
+
142
+ namespace = run_cart_2d(BlockingCartDataExchanger, reverse_axis=1)
143
+
144
+ assert namespace['success']
145
+
146
+ #===============================================================================
147
+ # RUN TEST MANUALLY
148
+ #===============================================================================
149
+ if __name__=='__main__':
150
+
151
+ locals().update( run_cart_2d( BlockingCartDataExchanger, verbose=True ) )
152
+
153
+ # Print error messages (if any) in orderly fashion
154
+ for k in range(size):
155
+ if k == rank and not success:
156
+ print( "Rank {}: wrong ghost cell data!".format( rank ), flush=True )
157
+ comm.Barrier()
158
+
159
+ if rank == 0:
160
+ if success_global:
161
+ print( "PASSED", end='\n\n', flush=True )
162
+ else:
163
+ print( "FAILED", end='\n\n', flush=True )
164
+