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.
- feectools/__init__.py +0 -0
- feectools/accelerate/__init__.py +0 -0
- feectools/accelerate/accelerate.py +220 -0
- feectools/accelerate/compile_psydac.mk +52 -0
- feectools/api/__init__.py +0 -0
- feectools/api/essential_bc.py +122 -0
- feectools/api/fem_bilinear_form.py +2226 -0
- feectools/api/fem_common.py +286 -0
- feectools/api/fem_sum_form.py +123 -0
- feectools/api/settings.py +82 -0
- feectools/core/__init__.py +11 -0
- feectools/core/bsplines.py +1107 -0
- feectools/core/bsplines_kernels.py +1349 -0
- feectools/core/field_evaluation_kernels.py +5015 -0
- feectools/core/tests/__init__.py +0 -0
- feectools/core/tests/test_bsplines.py +263 -0
- feectools/core/tests/test_bsplines_kernel.py +40 -0
- feectools/core/tests/test_bsplines_pyccel.py +752 -0
- feectools/ddm/__init__.py +3 -0
- feectools/ddm/basic.py +78 -0
- feectools/ddm/blocking_data_exchanger.py +348 -0
- feectools/ddm/cart.py +1835 -0
- feectools/ddm/interface_data_exchanger.py +122 -0
- feectools/ddm/mpi.py +109 -0
- feectools/ddm/nonblocking_data_exchanger.py +331 -0
- feectools/ddm/partition.py +207 -0
- feectools/ddm/petsc.py +112 -0
- feectools/ddm/tests/__init__.py +0 -0
- feectools/ddm/tests/test_cart_1d.py +138 -0
- feectools/ddm/tests/test_cart_2d.py +164 -0
- feectools/ddm/tests/test_cart_3d.py +158 -0
- feectools/ddm/tests/test_multicart_2d.py +173 -0
- feectools/ddm/tests/test_partition.py +124 -0
- feectools/ddm/utilities.py +24 -0
- feectools/feec/__init__.py +0 -0
- feectools/feec/derivatives.py +780 -0
- feectools/feec/dof_kernels.py +210 -0
- feectools/feec/global_geometric_projectors.py +1073 -0
- feectools/feec/hodge.py +148 -0
- feectools/fem/__init__.py +0 -0
- feectools/fem/basic.py +465 -0
- feectools/fem/grid.py +181 -0
- feectools/fem/partitioning.py +344 -0
- feectools/fem/projectors.py +160 -0
- feectools/fem/splines.py +559 -0
- feectools/fem/tensor.py +1393 -0
- feectools/fem/tests/__init__.py +0 -0
- feectools/fem/tests/analytical_profiles_1d.py +100 -0
- feectools/fem/tests/analytical_profiles_base.py +34 -0
- feectools/fem/tests/splines_error_bounds.py +155 -0
- feectools/fem/tests/test_spline_histopolation.py +120 -0
- feectools/fem/tests/test_spline_interpolation.py +182 -0
- feectools/fem/tests/test_splines.py +184 -0
- feectools/fem/tests/test_splines_par.py +46 -0
- feectools/fem/tests/test_vector_spaces.py +150 -0
- feectools/fem/tests/utilities.py +47 -0
- feectools/fem/vector.py +729 -0
- feectools/linalg/__init__.py +0 -0
- feectools/linalg/basic.py +1386 -0
- feectools/linalg/block.py +1451 -0
- feectools/linalg/direct_solvers.py +201 -0
- feectools/linalg/fft.py +258 -0
- feectools/linalg/kernels/__init__.py +0 -0
- feectools/linalg/kernels/axpy_kernels.py +57 -0
- feectools/linalg/kernels/inner_kernels.py +100 -0
- feectools/linalg/kernels/matvec_kernels.py +206 -0
- feectools/linalg/kernels/stencil2IJV_kernels.py +227 -0
- feectools/linalg/kernels/stencil2coo_kernels.py +179 -0
- feectools/linalg/kernels/transpose_kernels.py +263 -0
- feectools/linalg/kron.py +911 -0
- feectools/linalg/solvers.py +1914 -0
- feectools/linalg/sparse.py +114 -0
- feectools/linalg/stencil.py +2923 -0
- feectools/linalg/stencil_dot_kernels.py +317 -0
- feectools/linalg/stencil_transpose_kernels.py +372 -0
- feectools/linalg/tests/__init__.py +0 -0
- feectools/linalg/tests/test_block.py +1588 -0
- feectools/linalg/tests/test_fft.py +106 -0
- feectools/linalg/tests/test_kron_stencil_matrix.py +114 -0
- feectools/linalg/tests/test_linalg.py +1065 -0
- feectools/linalg/tests/test_matrix_free.py +128 -0
- feectools/linalg/tests/test_solvers.py +213 -0
- feectools/linalg/tests/test_stencil_interface_matrix.py +379 -0
- feectools/linalg/tests/test_stencil_vector.py +1036 -0
- feectools/linalg/tests/test_stencil_vector_space.py +440 -0
- feectools/linalg/topetsc.py +522 -0
- feectools/linalg/utilities.py +200 -0
- feectools/utilities/__init__.py +0 -0
- feectools/utilities/quadratures.py +113 -0
- feectools/utilities/utils.py +166 -0
- feectools/version.py +1 -0
- feectools-0.1.0.dist-info/METADATA +66 -0
- feectools-0.1.0.dist-info/RECORD +98 -0
- feectools-0.1.0.dist-info/WHEEL +5 -0
- feectools-0.1.0.dist-info/entry_points.txt +3 -0
- feectools-0.1.0.dist-info/licenses/AUTHORS +22 -0
- feectools-0.1.0.dist-info/licenses/LICENSE +21 -0
- 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
|
+
|