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,522 @@
|
|
|
1
|
+
from itertools import product as cartesian_prod
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
from feectools.linalg.basic import VectorSpace
|
|
6
|
+
from feectools.linalg.block import BlockVectorSpace, BlockVector, BlockLinearOperator
|
|
7
|
+
from feectools.linalg.stencil import StencilVectorSpace, StencilVector, StencilMatrix
|
|
8
|
+
from feectools.linalg.kernels.stencil2IJV_kernels import stencil2IJV_1d_C, stencil2IJV_2d_C, stencil2IJV_3d_C
|
|
9
|
+
|
|
10
|
+
__all__ = (
|
|
11
|
+
'petsc_local_to_psydac',
|
|
12
|
+
'psydac_to_petsc_global',
|
|
13
|
+
'get_npts_local',
|
|
14
|
+
'get_npts_per_block',
|
|
15
|
+
'vec_topetsc',
|
|
16
|
+
'mat_topetsc'
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
# Dictionary used to select the correct kernel function based on dimensionality
|
|
20
|
+
kernels = {
|
|
21
|
+
'stencil2IJV': {'F': None,
|
|
22
|
+
'C': (None, stencil2IJV_1d_C, stencil2IJV_2d_C, stencil2IJV_3d_C)}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_index_shift_per_block_per_process(V):
|
|
27
|
+
npts_local_per_block_per_process = np.array(get_npts_per_block(V)) #indexed [b,k,d] for block b and process k and dimension d
|
|
28
|
+
local_sizes_per_block_per_process = np.prod(npts_local_per_block_per_process, axis=-1) #indexed [b,k] for block b and process k
|
|
29
|
+
|
|
30
|
+
n_blocks = npts_local_per_block_per_process.shape[0]
|
|
31
|
+
n_procs = npts_local_per_block_per_process.shape[1]
|
|
32
|
+
|
|
33
|
+
index_shift_per_block_per_process = [[0 + np.sum(local_sizes_per_block_per_process[:,:k]) + np.sum(local_sizes_per_block_per_process[:b,k]) for k in range(n_procs)] for b in range(n_blocks)]
|
|
34
|
+
|
|
35
|
+
return index_shift_per_block_per_process #Global variable indexed as [b][k] fo block b, process k
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def toIJVrowmap(mat_block, bd, bc, I, J, V, rowmap, dspace, cspace, dnpts_block, cnpts_block, dshift_block, cshift_block, order='C'):
|
|
39
|
+
# Extract Cartesian decomposition of the Block where the node is:
|
|
40
|
+
dspace_block = dspace if isinstance(dspace, StencilVectorSpace) else dspace.spaces[bd]
|
|
41
|
+
cspace_block = cspace if isinstance(cspace, StencilVectorSpace) else cspace.spaces[bc]
|
|
42
|
+
|
|
43
|
+
# Shortcuts
|
|
44
|
+
cnl = [np.int64(n) for n in get_npts_local(cspace_block)[0]]
|
|
45
|
+
dng = [np.int64(n) for n in dspace_block.cart.npts]
|
|
46
|
+
cs = [np.int64(s) for s in cspace_block.cart.starts]
|
|
47
|
+
cp = [np.int64(p) for p in cspace_block.cart.pads]
|
|
48
|
+
cm = [np.int64(m) for m in cspace_block.cart.shifts]
|
|
49
|
+
dsh = np.array(dshift_block, dtype='int64')
|
|
50
|
+
csh = np.array(cshift_block, dtype='int64')
|
|
51
|
+
|
|
52
|
+
dgs = [np.array(gs, dtype='int64') for gs in dspace_block.cart.global_starts] # Global variable
|
|
53
|
+
dge = [np.array(ge, dtype='int64') for ge in dspace_block.cart.global_ends] # Global variable
|
|
54
|
+
cgs = [np.array(gs, dtype='int64') for gs in cspace_block.cart.global_starts] # Global variable
|
|
55
|
+
cge = [np.array(ge, dtype='int64') for ge in cspace_block.cart.global_ends] # Global variable
|
|
56
|
+
|
|
57
|
+
dnlb = [np.array([n[d] for n in dnpts_block], dtype='int64') for d in range(dspace_block.cart.ndim)]
|
|
58
|
+
cnlb = [np.array([n[d] for n in cnpts_block] , dtype='int64') for d in range(cspace_block.cart.ndim)]
|
|
59
|
+
|
|
60
|
+
# Range of data owned by local process (no ghost regions)
|
|
61
|
+
local = tuple( [slice(m*p,-m*p) for p,m in zip(cp, cm)] + [slice(None)] * dspace_block.cart.ndim )
|
|
62
|
+
shape = mat_block._data[local].shape
|
|
63
|
+
nrows = np.prod(shape[0:dspace_block.cart.ndim])
|
|
64
|
+
nentries = np.prod(shape)
|
|
65
|
+
|
|
66
|
+
# locally block I, J, V, rowmap storage
|
|
67
|
+
Ib = np.zeros(nrows + 1, dtype='int64')
|
|
68
|
+
Jb = np.zeros(nentries, dtype='int64')
|
|
69
|
+
rowmapb = np.zeros(nrows, dtype='int64')
|
|
70
|
+
Vb = np.zeros(nentries, dtype=mat_block._data.dtype)
|
|
71
|
+
|
|
72
|
+
Ib[0] += I[-1]
|
|
73
|
+
|
|
74
|
+
stencil2IJV = kernels['stencil2IJV'][order][dspace_block.cart.ndim]
|
|
75
|
+
|
|
76
|
+
nnz_rows, nnz = stencil2IJV(mat_block._data, Ib, Jb, Vb, rowmapb,
|
|
77
|
+
*cnl, *dng, *cs, *cp, *cm,
|
|
78
|
+
dsh, csh, *dgs, *dge, *cgs, *cge, *dnlb, *cnlb
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
I += list(Ib[1:nnz_rows + 1])
|
|
82
|
+
rowmap += list(rowmapb[:nnz_rows])
|
|
83
|
+
J += list(Jb[:nnz])
|
|
84
|
+
V += list(Vb[:nnz])
|
|
85
|
+
|
|
86
|
+
return I, J, V, rowmap
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def petsc_local_to_psydac(
|
|
90
|
+
V : VectorSpace,
|
|
91
|
+
petsc_index : int):
|
|
92
|
+
"""
|
|
93
|
+
Convert the PETSc local index (starting from 0 in each process) to a Psydac local index (natural multi-index, as grid coordinates).
|
|
94
|
+
|
|
95
|
+
Parameters
|
|
96
|
+
-----------
|
|
97
|
+
V : VectorSpace
|
|
98
|
+
The vector space to which the Psydac vector belongs.
|
|
99
|
+
This defines the number of blocks, the size of each block,
|
|
100
|
+
and how each block is distributed across MPI processes.
|
|
101
|
+
|
|
102
|
+
petsc_index : int
|
|
103
|
+
The local PETSc index. The 0 index is only owned by every process.
|
|
104
|
+
|
|
105
|
+
Returns
|
|
106
|
+
--------
|
|
107
|
+
block: tuple
|
|
108
|
+
The block where the Psydac multi-index belongs to.
|
|
109
|
+
psydac_index : tuple
|
|
110
|
+
The Psydac local multi-index. This index is local the block.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
# Get the number of points for each block and each dimension local to the current process:
|
|
114
|
+
npts_local_per_block = np.array(get_npts_local(V)) # indexed [b,d] for block b and dimension d
|
|
115
|
+
# Get the local size of the current process for each block:
|
|
116
|
+
local_sizes_per_block = np.prod(npts_local_per_block, axis=-1) # indexed [b] for block b
|
|
117
|
+
# Compute the accumulated local size of the current process for each block:
|
|
118
|
+
accumulated_local_sizes_per_block = np.concatenate((np.zeros((1,), dtype=int), np.cumsum(local_sizes_per_block, axis=0))) #indexed [b+1] for block b
|
|
119
|
+
|
|
120
|
+
n_blocks = local_sizes_per_block.size
|
|
121
|
+
|
|
122
|
+
# Find the block where the index belongs to:
|
|
123
|
+
bb = np.nonzero(
|
|
124
|
+
np.array(
|
|
125
|
+
[petsc_index in range(accumulated_local_sizes_per_block[b], accumulated_local_sizes_per_block[b+1])
|
|
126
|
+
for b in range(n_blocks)]
|
|
127
|
+
))[0][0]
|
|
128
|
+
|
|
129
|
+
if isinstance(V, BlockVectorSpace):
|
|
130
|
+
V = V.spaces[bb]
|
|
131
|
+
|
|
132
|
+
ndim = V.ndim
|
|
133
|
+
p = V.pads
|
|
134
|
+
m = V.shifts
|
|
135
|
+
|
|
136
|
+
# Get the number of points for each dimension local to the current process and block:
|
|
137
|
+
npts_local = npts_local_per_block[bb]
|
|
138
|
+
|
|
139
|
+
# Get the PETSc index local within the block:
|
|
140
|
+
petsc_index -= accumulated_local_sizes_per_block[bb]
|
|
141
|
+
|
|
142
|
+
ii = np.zeros((ndim,), dtype=int)
|
|
143
|
+
if ndim == 1:
|
|
144
|
+
ii[0] = petsc_index + p[0]*m[0]
|
|
145
|
+
|
|
146
|
+
elif ndim == 2:
|
|
147
|
+
ii[0] = petsc_index // npts_local[1] + p[0]*m[0]
|
|
148
|
+
ii[1] = petsc_index % npts_local[1] + p[1]*m[1]
|
|
149
|
+
|
|
150
|
+
elif ndim == 3:
|
|
151
|
+
ii[0] = petsc_index // (npts_local[1]*npts_local[2]) + p[0]*m[0]
|
|
152
|
+
ii[1] = petsc_index // npts_local[2] + p[1]*m[1] - npts_local[1]*(ii[0] - p[0]*m[0])
|
|
153
|
+
ii[2] = petsc_index % npts_local[2] + p[2]*m[2]
|
|
154
|
+
|
|
155
|
+
else:
|
|
156
|
+
raise NotImplementedError( "Cannot handle more than 3 dimensions." )
|
|
157
|
+
|
|
158
|
+
return (bb,), tuple(ii)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def psydac_to_petsc_global(
|
|
162
|
+
V : VectorSpace,
|
|
163
|
+
block_indices,
|
|
164
|
+
ndarray_indices) -> int:
|
|
165
|
+
"""
|
|
166
|
+
Convert the Psydac local index (natural multi-index, as grid coordinates) to a PETSc global index. Performs a search to find the process owning the multi-index.
|
|
167
|
+
|
|
168
|
+
Parameters
|
|
169
|
+
-----------
|
|
170
|
+
V : VectorSpace
|
|
171
|
+
The vector space to which the Psydac vector belongs.
|
|
172
|
+
This defines the number of blocks, the size of each block,
|
|
173
|
+
and how each block is distributed across MPI processes.
|
|
174
|
+
|
|
175
|
+
block_indices : tuple[int]
|
|
176
|
+
The indices which identify the block in a (possibly nested) block vector.
|
|
177
|
+
In the case of a StencilVector this is an empty tuple.
|
|
178
|
+
|
|
179
|
+
ndarray_indices : tuple[int]
|
|
180
|
+
The multi-index which identifies an element in the _data array,
|
|
181
|
+
excluding the ghost regions.
|
|
182
|
+
|
|
183
|
+
Returns
|
|
184
|
+
--------
|
|
185
|
+
petsc_index : int
|
|
186
|
+
The global PETSc index. The 0 index is only owned by the first process.
|
|
187
|
+
"""
|
|
188
|
+
|
|
189
|
+
bb = block_indices[0]
|
|
190
|
+
# Get the number of points per block, per process and per dimension:
|
|
191
|
+
npts_local_per_block_per_process = np.array(get_npts_per_block(V)) #indexed [b,k,d] for block b and process k and dimension d
|
|
192
|
+
# Get the local sizes per block and per process:
|
|
193
|
+
local_sizes_per_block_per_process = np.prod(npts_local_per_block_per_process, axis=-1) #indexed [b,k] for block b and process k
|
|
194
|
+
|
|
195
|
+
# Extract Cartesian decomposition of the Block where the node is:
|
|
196
|
+
if isinstance(V, BlockVectorSpace):
|
|
197
|
+
V = V.spaces[bb]
|
|
198
|
+
|
|
199
|
+
cart = V.cart
|
|
200
|
+
|
|
201
|
+
nprocs = cart.nprocs # Number of processes in each dimension
|
|
202
|
+
ndim = cart.ndim
|
|
203
|
+
|
|
204
|
+
# Get global starts and ends to find process owning the node.
|
|
205
|
+
gs = cart.global_starts # Global variable
|
|
206
|
+
ge = cart.global_ends # Global variable
|
|
207
|
+
|
|
208
|
+
jj = ndarray_indices
|
|
209
|
+
|
|
210
|
+
if ndim == 1:
|
|
211
|
+
if cart.comm:
|
|
212
|
+
# Find to which process the node belongs to:
|
|
213
|
+
proc_index = np.nonzero(np.array([jj[0] in range(gs[0][k],ge[0][k]+1) for k in range(gs[0].size)]))[0][0]
|
|
214
|
+
else:
|
|
215
|
+
proc_index = 0
|
|
216
|
+
|
|
217
|
+
# Find the index shift corresponding to the block and the owner process:
|
|
218
|
+
index_shift = 0 + np.sum(local_sizes_per_block_per_process[:,:proc_index]) + np.sum(local_sizes_per_block_per_process[:bb,proc_index])
|
|
219
|
+
|
|
220
|
+
# Compute the global PETSc index:
|
|
221
|
+
global_index = index_shift + jj[0] - gs[0][proc_index]
|
|
222
|
+
|
|
223
|
+
elif ndim == 2:
|
|
224
|
+
if cart.comm:
|
|
225
|
+
# Find to which process the node belongs to:
|
|
226
|
+
proc_x = np.nonzero(np.array([jj[0] in range(gs[0][k],ge[0][k]+1) for k in range(gs[0].size)]))[0][0]
|
|
227
|
+
proc_y = np.nonzero(np.array([jj[1] in range(gs[1][k],ge[1][k]+1) for k in range(gs[1].size)]))[0][0]
|
|
228
|
+
else:
|
|
229
|
+
proc_x = 0
|
|
230
|
+
proc_y = 0
|
|
231
|
+
|
|
232
|
+
proc_index = proc_y + proc_x*nprocs[1]
|
|
233
|
+
# Find the index shift corresponding to the block and the owner process:
|
|
234
|
+
index_shift = 0 + np.sum(local_sizes_per_block_per_process[:,:proc_index]) + np.sum(local_sizes_per_block_per_process[:bb,proc_index])
|
|
235
|
+
|
|
236
|
+
# Compute the global PETSc index:
|
|
237
|
+
global_index = index_shift + jj[1] - gs[1][proc_y] + (jj[0] - gs[0][proc_x]) * npts_local_per_block_per_process[bb,proc_index,1]
|
|
238
|
+
|
|
239
|
+
elif ndim == 3:
|
|
240
|
+
if cart.comm:
|
|
241
|
+
# Find to which process the node belongs to:
|
|
242
|
+
proc_x = np.nonzero(np.array([jj[0] in range(gs[0][k],ge[0][k]+1) for k in range(gs[0].size)]))[0][0]
|
|
243
|
+
proc_y = np.nonzero(np.array([jj[1] in range(gs[1][k],ge[1][k]+1) for k in range(gs[1].size)]))[0][0]
|
|
244
|
+
proc_z = np.nonzero(np.array([jj[2] in range(gs[2][k],ge[2][k]+1) for k in range(gs[2].size)]))[0][0]
|
|
245
|
+
else:
|
|
246
|
+
proc_x = 0
|
|
247
|
+
proc_y = 0
|
|
248
|
+
proc_z = 0
|
|
249
|
+
|
|
250
|
+
proc_index = proc_z + proc_y*nprocs[2] + proc_x*nprocs[1]*nprocs[2]
|
|
251
|
+
|
|
252
|
+
# Find the index shift corresponding to the block and the owner process:
|
|
253
|
+
index_shift = 0 + np.sum(local_sizes_per_block_per_process[:,:proc_index]) + np.sum(local_sizes_per_block_per_process[:bb,proc_index])
|
|
254
|
+
|
|
255
|
+
# Compute the global PETSc index:
|
|
256
|
+
global_index = index_shift \
|
|
257
|
+
+ jj[2] - gs[2][proc_z] \
|
|
258
|
+
+ (jj[1] - gs[1][proc_y]) * npts_local_per_block_per_process[bb][proc_index][2] \
|
|
259
|
+
+ (jj[0] - gs[0][proc_x]) * npts_local_per_block_per_process[bb][proc_index][1] * npts_local_per_block_per_process[bb][proc_index][2]
|
|
260
|
+
|
|
261
|
+
else:
|
|
262
|
+
raise NotImplementedError( "Cannot handle more than 3 dimensions." )
|
|
263
|
+
|
|
264
|
+
return global_index
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def get_npts_local(V : VectorSpace) -> list:
|
|
268
|
+
"""
|
|
269
|
+
Compute the local number of nodes per dimension owned by the actual process.
|
|
270
|
+
This is a local variable, its value will be different for each process.
|
|
271
|
+
|
|
272
|
+
Parameter
|
|
273
|
+
---------
|
|
274
|
+
V : VectorSpace
|
|
275
|
+
The distributed Psydac vector space.
|
|
276
|
+
|
|
277
|
+
Returns
|
|
278
|
+
--------
|
|
279
|
+
list
|
|
280
|
+
Local number of nodes per dimension owned by the actual process.
|
|
281
|
+
In case of a StencilVectorSpace the list contains a single list with length equal the number of dimensions in the domain.
|
|
282
|
+
In case of a BlockVectorSpace the list has length equal the number of blocks.
|
|
283
|
+
"""
|
|
284
|
+
if isinstance(V, StencilVectorSpace):
|
|
285
|
+
s = V.starts
|
|
286
|
+
e = V.ends
|
|
287
|
+
npts_local = [ e - s + 1 for s, e in zip(s, e)] #Number of points in each dimension within each process. Different for each process.
|
|
288
|
+
return [npts_local]
|
|
289
|
+
|
|
290
|
+
npts_local_per_block = []
|
|
291
|
+
for b in range(V.n_blocks):
|
|
292
|
+
npts_local_b = get_npts_local(V.spaces[b])
|
|
293
|
+
if isinstance(V.spaces[b], StencilVectorSpace):
|
|
294
|
+
npts_local_b = npts_local_b[0]
|
|
295
|
+
npts_local_per_block.append(npts_local_b)
|
|
296
|
+
|
|
297
|
+
return npts_local_per_block
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def get_npts_per_block(V : VectorSpace) -> list:
|
|
301
|
+
"""
|
|
302
|
+
Compute the number of nodes per block, process and dimension.
|
|
303
|
+
This is a global variable, its value is the same for all processes.
|
|
304
|
+
|
|
305
|
+
Parameter
|
|
306
|
+
---------
|
|
307
|
+
V : VectorSpace
|
|
308
|
+
The distributed Psydac vector space.
|
|
309
|
+
|
|
310
|
+
Returns
|
|
311
|
+
--------
|
|
312
|
+
list
|
|
313
|
+
Number of nodes per block, process and dimension.
|
|
314
|
+
"""
|
|
315
|
+
if isinstance(V, StencilVectorSpace):
|
|
316
|
+
gs = V.cart.global_starts # Global variable
|
|
317
|
+
ge = V.cart.global_ends # Global variable
|
|
318
|
+
npts_local_perprocess = [ ge_i - gs_i + 1 for gs_i, ge_i in zip(gs, ge)] #Global variable
|
|
319
|
+
|
|
320
|
+
#if V.cart.comm:
|
|
321
|
+
npts_local_perprocess = [*cartesian_prod(*npts_local_perprocess)] #Global variable
|
|
322
|
+
|
|
323
|
+
return [npts_local_perprocess]
|
|
324
|
+
|
|
325
|
+
npts_local_per_block = []
|
|
326
|
+
for b in range(V.n_blocks):
|
|
327
|
+
npts_b = get_npts_per_block(V.spaces[b])
|
|
328
|
+
if isinstance(V.spaces[b], StencilVectorSpace):
|
|
329
|
+
npts_b = npts_b[0]
|
|
330
|
+
npts_local_per_block.append(npts_b)
|
|
331
|
+
|
|
332
|
+
return npts_local_per_block
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def vec_topetsc(vec):
|
|
336
|
+
""" Convert vector from Psydac format to a PETSc.Vec object.
|
|
337
|
+
|
|
338
|
+
Parameters
|
|
339
|
+
----------
|
|
340
|
+
vec : feectools.linalg.stencil.StencilVector | feectools.linalg.block.BlockVector
|
|
341
|
+
Psydac StencilVector or BlockVector. In the case of a BlockVector, only the case where the blocks are StencilVector is implemented.
|
|
342
|
+
|
|
343
|
+
Returns
|
|
344
|
+
-------
|
|
345
|
+
gvec : PETSc.Vec
|
|
346
|
+
PETSc vector
|
|
347
|
+
"""
|
|
348
|
+
from petsc4py import PETSc
|
|
349
|
+
|
|
350
|
+
if isinstance(vec.space, BlockVectorSpace) and any([isinstance(vec.space.spaces[b], BlockVectorSpace) for b in range(len(vec.space.spaces))]):
|
|
351
|
+
raise NotImplementedError('Conversion for block of blocks not implemented.')
|
|
352
|
+
|
|
353
|
+
if isinstance(vec, StencilVector):
|
|
354
|
+
carts = [vec.space.cart]
|
|
355
|
+
elif isinstance(vec.space, BlockVectorSpace):
|
|
356
|
+
carts = []
|
|
357
|
+
for b in range(vec.n_blocks):
|
|
358
|
+
carts.append(vec.space.spaces[b].cart)
|
|
359
|
+
|
|
360
|
+
n_blocks = 1 if isinstance(vec, StencilVector) else vec.n_blocks
|
|
361
|
+
|
|
362
|
+
# Get the number of points local to the current process:
|
|
363
|
+
npts_local = get_npts_local(vec.space) # indexed [block, dimension]. Different for each process.
|
|
364
|
+
|
|
365
|
+
# Number of dimensions for each cart:
|
|
366
|
+
ndims = [cart.ndim for cart in carts]
|
|
367
|
+
|
|
368
|
+
globalsize = vec.space.dimension
|
|
369
|
+
|
|
370
|
+
# Sum over the blocks to get the total local size
|
|
371
|
+
localsize = np.sum(np.prod(npts_local, axis=1))
|
|
372
|
+
|
|
373
|
+
gvec = PETSc.Vec().create(comm=carts[0].global_comm)
|
|
374
|
+
|
|
375
|
+
# Set global and local size:
|
|
376
|
+
gvec.setSizes(size=(localsize, globalsize))
|
|
377
|
+
|
|
378
|
+
gvec.setFromOptions()
|
|
379
|
+
gvec.setUp()
|
|
380
|
+
|
|
381
|
+
petsc_indices = []
|
|
382
|
+
petsc_data = []
|
|
383
|
+
|
|
384
|
+
vec_block = vec
|
|
385
|
+
|
|
386
|
+
for b in range(n_blocks):
|
|
387
|
+
if isinstance(vec, BlockVector):
|
|
388
|
+
vec_block = vec.blocks[b]
|
|
389
|
+
|
|
390
|
+
s = carts[b].starts
|
|
391
|
+
ghost_size = [pi*mi for pi,mi in zip(carts[b].pads, carts[b].shifts)]
|
|
392
|
+
|
|
393
|
+
if ndims[b] == 1:
|
|
394
|
+
for i1 in range(npts_local[b][0]):
|
|
395
|
+
value = vec_block._data[i1 + ghost_size[0]]
|
|
396
|
+
if value != 0:
|
|
397
|
+
i1_n = s[0] + i1
|
|
398
|
+
i_g = psydac_to_petsc_global(vec.space, (b,), (i1_n,))
|
|
399
|
+
petsc_indices.append(i_g)
|
|
400
|
+
petsc_data.append(value)
|
|
401
|
+
|
|
402
|
+
elif ndims[b] == 2:
|
|
403
|
+
for i1 in range(npts_local[b][0]):
|
|
404
|
+
for i2 in range(npts_local[b][1]):
|
|
405
|
+
value = vec_block._data[i1 + ghost_size[0], i2 + ghost_size[1]]
|
|
406
|
+
if value != 0:
|
|
407
|
+
i1_n = s[0] + i1
|
|
408
|
+
i2_n = s[1] + i2
|
|
409
|
+
i_g = psydac_to_petsc_global(vec.space, (b,), (i1_n, i2_n))
|
|
410
|
+
petsc_indices.append(i_g)
|
|
411
|
+
petsc_data.append(value)
|
|
412
|
+
|
|
413
|
+
elif ndims[b] == 3:
|
|
414
|
+
for i1 in np.arange(npts_local[b][0]):
|
|
415
|
+
for i2 in np.arange(npts_local[b][1]):
|
|
416
|
+
for i3 in np.arange(npts_local[b][2]):
|
|
417
|
+
value = vec_block._data[i1 + ghost_size[0], i2 + ghost_size[1], i3 + ghost_size[2]]
|
|
418
|
+
if value != 0:
|
|
419
|
+
i1_n = s[0] + i1
|
|
420
|
+
i2_n = s[1] + i2
|
|
421
|
+
i3_n = s[2] + i3
|
|
422
|
+
i_g = psydac_to_petsc_global(vec.space, (b,), (i1_n, i2_n, i3_n))
|
|
423
|
+
petsc_indices.append(i_g)
|
|
424
|
+
petsc_data.append(value)
|
|
425
|
+
|
|
426
|
+
# Set the values. The values are stored in a cache memory.
|
|
427
|
+
gvec.setValues(petsc_indices, petsc_data, addv=PETSc.InsertMode.ADD_VALUES) #The addition mode the values is necessary when periodic BC
|
|
428
|
+
|
|
429
|
+
# Assemble vector with the values from the cache. Here it is where PETSc exchanges global communication.
|
|
430
|
+
gvec.assemble()
|
|
431
|
+
|
|
432
|
+
return gvec
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def mat_topetsc(mat):
|
|
436
|
+
""" Convert operator from Psydac format to a PETSc.Mat object.
|
|
437
|
+
|
|
438
|
+
Parameters
|
|
439
|
+
----------
|
|
440
|
+
mat : feectools.linalg.stencil.StencilMatrix | feectools.linalg.block.BlockLinearOperator
|
|
441
|
+
Psydac operator. In the case of a BlockLinearOperator, only the case where the blocks are StencilMatrix is implemented.
|
|
442
|
+
|
|
443
|
+
Returns
|
|
444
|
+
-------
|
|
445
|
+
gmat : PETSc.Mat
|
|
446
|
+
PETSc Matrix
|
|
447
|
+
"""
|
|
448
|
+
|
|
449
|
+
from petsc4py import PETSc
|
|
450
|
+
|
|
451
|
+
assert isinstance(mat, StencilMatrix) or isinstance(mat, BlockLinearOperator), 'Conversion only implemented for StencilMatrix and BlockLinearOperator.'
|
|
452
|
+
|
|
453
|
+
if (isinstance(mat.domain, BlockVectorSpace) and any([isinstance(mat.domain.spaces[b], BlockVectorSpace) for b in range(len(mat.domain.spaces))]))\
|
|
454
|
+
or (isinstance(mat.codomain, BlockVectorSpace) and any([isinstance(mat.codomain.spaces[b], BlockVectorSpace) for b in range(len(mat.codomain.spaces))])):
|
|
455
|
+
raise NotImplementedError('Conversion for block of blocks not implemented.')
|
|
456
|
+
|
|
457
|
+
if isinstance(mat.domain, StencilVectorSpace):
|
|
458
|
+
comm = mat.domain.cart.global_comm
|
|
459
|
+
elif isinstance(mat.domain, BlockVectorSpace):
|
|
460
|
+
comm = mat.domain.spaces[0].cart.global_comm
|
|
461
|
+
|
|
462
|
+
nonzero_block_indices = ((0,0),) if isinstance(mat, StencilMatrix) else mat.nonzero_block_indices
|
|
463
|
+
|
|
464
|
+
mat.update_ghost_regions()
|
|
465
|
+
mat.remove_spurious_entries()
|
|
466
|
+
|
|
467
|
+
# Get the number of points local to the current process:
|
|
468
|
+
dnpts_local = get_npts_local(mat.domain) # indexed [block, dimension]. Different for each process.
|
|
469
|
+
cnpts_local = get_npts_local(mat.codomain) # indexed [block, dimension]. Different for each process.
|
|
470
|
+
|
|
471
|
+
# Get the number of points per block, per process and per dimension:
|
|
472
|
+
dnpts_per_block_per_process = np.array(get_npts_per_block(mat.domain)) # global variable, indexed as [block, process, dimension]
|
|
473
|
+
cnpts_per_block_per_process = np.array(get_npts_per_block(mat.codomain)) # global variable, indexed as [block, process, dimension]
|
|
474
|
+
|
|
475
|
+
# Get the index shift for each block and each process:
|
|
476
|
+
dindex_shift = get_index_shift_per_block_per_process(mat.domain) # global variable, indexed as [block, process, dimension]
|
|
477
|
+
cindex_shift = get_index_shift_per_block_per_process(mat.codomain) # global variable, indexed as [block, process, dimension]
|
|
478
|
+
|
|
479
|
+
globalsize = mat.shape
|
|
480
|
+
|
|
481
|
+
# Sum over the blocks to get the total local size
|
|
482
|
+
localsize = (np.sum(np.prod(cnpts_local, axis=1)), np.sum(np.prod(dnpts_local, axis=1)))
|
|
483
|
+
|
|
484
|
+
gmat = PETSc.Mat().create(comm=comm)
|
|
485
|
+
|
|
486
|
+
# Set global and local sizes: size=((local_rows, rows), (local_columns, columns))
|
|
487
|
+
gmat.setSizes(size=((localsize[0], globalsize[0]), (localsize[1], globalsize[1])))
|
|
488
|
+
|
|
489
|
+
if comm:
|
|
490
|
+
# Set PETSc sparse parallel matrix type
|
|
491
|
+
gmat.setType("mpiaij")
|
|
492
|
+
else:
|
|
493
|
+
# Set PETSc sparse sequential matrix type
|
|
494
|
+
gmat.setType("seqaij")
|
|
495
|
+
|
|
496
|
+
gmat.setFromOptions()
|
|
497
|
+
gmat.setUp()
|
|
498
|
+
|
|
499
|
+
I = [0] # Row pointers
|
|
500
|
+
J = [] # Column indices
|
|
501
|
+
V = [] # Values
|
|
502
|
+
rowmap = [] # Row indices of rows containing non-zeros
|
|
503
|
+
|
|
504
|
+
mat_block = mat
|
|
505
|
+
|
|
506
|
+
for bc, bd in nonzero_block_indices:
|
|
507
|
+
if isinstance(mat, BlockLinearOperator):
|
|
508
|
+
mat_block = mat.blocks[bc][bd]
|
|
509
|
+
dnpts_block = dnpts_per_block_per_process[bd]
|
|
510
|
+
cnpts_block = cnpts_per_block_per_process[bc]
|
|
511
|
+
dshift_block = dindex_shift[bd]
|
|
512
|
+
cshift_block = cindex_shift[bc]
|
|
513
|
+
|
|
514
|
+
I,J,V,rowmap = toIJVrowmap(mat_block, bd, bc, I, J, V, rowmap, mat.domain, mat.codomain, dnpts_block, cnpts_block, dshift_block, cshift_block)
|
|
515
|
+
|
|
516
|
+
# Set the values using IJV&rowmap format. The values are stored in a cache memory.
|
|
517
|
+
gmat.setValuesIJV(I, J, V, rowmap=rowmap, addv=PETSc.InsertMode.ADD_VALUES) # The addition mode is necessary when periodic BC
|
|
518
|
+
|
|
519
|
+
# Assemble the matrix with the values from the cache. Here it is where PETSc exchanges global communication.
|
|
520
|
+
gmat.assemble()
|
|
521
|
+
|
|
522
|
+
return gmat
|