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,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