feectools 0.1.11__tar.gz → 0.2.0__tar.gz

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 (109) hide show
  1. {feectools-0.1.11/feectools.egg-info → feectools-0.2.0}/PKG-INFO +1 -1
  2. {feectools-0.1.11 → feectools-0.2.0}/feectools/linalg/basic.py +137 -27
  3. {feectools-0.1.11 → feectools-0.2.0}/feectools/linalg/fft.py +0 -6
  4. {feectools-0.1.11 → feectools-0.2.0}/feectools/linalg/kron.py +0 -6
  5. feectools-0.2.0/feectools/linalg/tests/test_toarray.py +162 -0
  6. {feectools-0.1.11 → feectools-0.2.0/feectools.egg-info}/PKG-INFO +1 -1
  7. {feectools-0.1.11 → feectools-0.2.0}/feectools.egg-info/SOURCES.txt +1 -0
  8. {feectools-0.1.11 → feectools-0.2.0}/pyproject.toml +1 -1
  9. {feectools-0.1.11 → feectools-0.2.0}/AUTHORS +0 -0
  10. {feectools-0.1.11 → feectools-0.2.0}/LICENSE +0 -0
  11. {feectools-0.1.11 → feectools-0.2.0}/README.md +0 -0
  12. {feectools-0.1.11 → feectools-0.2.0}/feectools/__init__.py +0 -0
  13. {feectools-0.1.11 → feectools-0.2.0}/feectools/accelerate/__init__.py +0 -0
  14. {feectools-0.1.11 → feectools-0.2.0}/feectools/accelerate/accelerate.py +0 -0
  15. {feectools-0.1.11 → feectools-0.2.0}/feectools/accelerate/compile_psydac.mk +0 -0
  16. {feectools-0.1.11 → feectools-0.2.0}/feectools/api/__init__.py +0 -0
  17. {feectools-0.1.11 → feectools-0.2.0}/feectools/api/essential_bc.py +0 -0
  18. {feectools-0.1.11 → feectools-0.2.0}/feectools/api/fem_bilinear_form.py +0 -0
  19. {feectools-0.1.11 → feectools-0.2.0}/feectools/api/fem_common.py +0 -0
  20. {feectools-0.1.11 → feectools-0.2.0}/feectools/api/fem_sum_form.py +0 -0
  21. {feectools-0.1.11 → feectools-0.2.0}/feectools/api/settings.py +0 -0
  22. {feectools-0.1.11 → feectools-0.2.0}/feectools/core/__init__.py +0 -0
  23. {feectools-0.1.11 → feectools-0.2.0}/feectools/core/bsplines.py +0 -0
  24. {feectools-0.1.11 → feectools-0.2.0}/feectools/core/bsplines_kernels.py +0 -0
  25. {feectools-0.1.11 → feectools-0.2.0}/feectools/core/field_evaluation_kernels.py +0 -0
  26. {feectools-0.1.11 → feectools-0.2.0}/feectools/core/tests/__init__.py +0 -0
  27. {feectools-0.1.11 → feectools-0.2.0}/feectools/core/tests/test_bsplines.py +0 -0
  28. {feectools-0.1.11 → feectools-0.2.0}/feectools/core/tests/test_bsplines_kernel.py +0 -0
  29. {feectools-0.1.11 → feectools-0.2.0}/feectools/core/tests/test_bsplines_pyccel.py +0 -0
  30. {feectools-0.1.11 → feectools-0.2.0}/feectools/ddm/__init__.py +0 -0
  31. {feectools-0.1.11 → feectools-0.2.0}/feectools/ddm/basic.py +0 -0
  32. {feectools-0.1.11 → feectools-0.2.0}/feectools/ddm/blocking_data_exchanger.py +0 -0
  33. {feectools-0.1.11 → feectools-0.2.0}/feectools/ddm/cart.py +0 -0
  34. {feectools-0.1.11 → feectools-0.2.0}/feectools/ddm/interface_data_exchanger.py +0 -0
  35. {feectools-0.1.11 → feectools-0.2.0}/feectools/ddm/mpi.py +0 -0
  36. {feectools-0.1.11 → feectools-0.2.0}/feectools/ddm/nonblocking_data_exchanger.py +0 -0
  37. {feectools-0.1.11 → feectools-0.2.0}/feectools/ddm/partition.py +0 -0
  38. {feectools-0.1.11 → feectools-0.2.0}/feectools/ddm/petsc.py +0 -0
  39. {feectools-0.1.11 → feectools-0.2.0}/feectools/ddm/tests/__init__.py +0 -0
  40. {feectools-0.1.11 → feectools-0.2.0}/feectools/ddm/tests/test_cart_1d.py +0 -0
  41. {feectools-0.1.11 → feectools-0.2.0}/feectools/ddm/tests/test_cart_2d.py +0 -0
  42. {feectools-0.1.11 → feectools-0.2.0}/feectools/ddm/tests/test_cart_3d.py +0 -0
  43. {feectools-0.1.11 → feectools-0.2.0}/feectools/ddm/tests/test_multicart_2d.py +0 -0
  44. {feectools-0.1.11 → feectools-0.2.0}/feectools/ddm/tests/test_partition.py +0 -0
  45. {feectools-0.1.11 → feectools-0.2.0}/feectools/ddm/utilities.py +0 -0
  46. {feectools-0.1.11 → feectools-0.2.0}/feectools/feec/__init__.py +0 -0
  47. {feectools-0.1.11 → feectools-0.2.0}/feectools/feec/derivatives.py +0 -0
  48. {feectools-0.1.11 → feectools-0.2.0}/feectools/feec/dof_kernels.py +0 -0
  49. {feectools-0.1.11 → feectools-0.2.0}/feectools/feec/global_geometric_projectors.py +0 -0
  50. {feectools-0.1.11 → feectools-0.2.0}/feectools/feec/hodge.py +0 -0
  51. {feectools-0.1.11 → feectools-0.2.0}/feectools/fem/__init__.py +0 -0
  52. {feectools-0.1.11 → feectools-0.2.0}/feectools/fem/basic.py +0 -0
  53. {feectools-0.1.11 → feectools-0.2.0}/feectools/fem/grid.py +0 -0
  54. {feectools-0.1.11 → feectools-0.2.0}/feectools/fem/lst_preconditioner.py +0 -0
  55. {feectools-0.1.11 → feectools-0.2.0}/feectools/fem/partitioning.py +0 -0
  56. {feectools-0.1.11 → feectools-0.2.0}/feectools/fem/projectors.py +0 -0
  57. {feectools-0.1.11 → feectools-0.2.0}/feectools/fem/splines.py +0 -0
  58. {feectools-0.1.11 → feectools-0.2.0}/feectools/fem/tensor.py +0 -0
  59. {feectools-0.1.11 → feectools-0.2.0}/feectools/fem/tests/__init__.py +0 -0
  60. {feectools-0.1.11 → feectools-0.2.0}/feectools/fem/tests/analytical_profiles_1d.py +0 -0
  61. {feectools-0.1.11 → feectools-0.2.0}/feectools/fem/tests/analytical_profiles_base.py +0 -0
  62. {feectools-0.1.11 → feectools-0.2.0}/feectools/fem/tests/splines_error_bounds.py +0 -0
  63. {feectools-0.1.11 → feectools-0.2.0}/feectools/fem/tests/test_dirichlet_projectors.py +0 -0
  64. {feectools-0.1.11 → feectools-0.2.0}/feectools/fem/tests/test_spline_histopolation.py +0 -0
  65. {feectools-0.1.11 → feectools-0.2.0}/feectools/fem/tests/test_spline_interpolation.py +0 -0
  66. {feectools-0.1.11 → feectools-0.2.0}/feectools/fem/tests/test_splines.py +0 -0
  67. {feectools-0.1.11 → feectools-0.2.0}/feectools/fem/tests/test_splines_par.py +0 -0
  68. {feectools-0.1.11 → feectools-0.2.0}/feectools/fem/tests/test_tensor.py +0 -0
  69. {feectools-0.1.11 → feectools-0.2.0}/feectools/fem/tests/test_vector_spaces.py +0 -0
  70. {feectools-0.1.11 → feectools-0.2.0}/feectools/fem/tests/utilities.py +0 -0
  71. {feectools-0.1.11 → feectools-0.2.0}/feectools/fem/vector.py +0 -0
  72. {feectools-0.1.11 → feectools-0.2.0}/feectools/linalg/__init__.py +0 -0
  73. {feectools-0.1.11 → feectools-0.2.0}/feectools/linalg/block.py +0 -0
  74. {feectools-0.1.11 → feectools-0.2.0}/feectools/linalg/direct_solvers.py +0 -0
  75. {feectools-0.1.11 → feectools-0.2.0}/feectools/linalg/kernels/__init__.py +0 -0
  76. {feectools-0.1.11 → feectools-0.2.0}/feectools/linalg/kernels/axpy_kernels.py +0 -0
  77. {feectools-0.1.11 → feectools-0.2.0}/feectools/linalg/kernels/inner_kernels.py +0 -0
  78. {feectools-0.1.11 → feectools-0.2.0}/feectools/linalg/kernels/matvec_kernels.py +0 -0
  79. {feectools-0.1.11 → feectools-0.2.0}/feectools/linalg/kernels/stencil2IJV_kernels.py +0 -0
  80. {feectools-0.1.11 → feectools-0.2.0}/feectools/linalg/kernels/stencil2coo_kernels.py +0 -0
  81. {feectools-0.1.11 → feectools-0.2.0}/feectools/linalg/kernels/transpose_kernels.py +0 -0
  82. {feectools-0.1.11 → feectools-0.2.0}/feectools/linalg/memory.py +0 -0
  83. {feectools-0.1.11 → feectools-0.2.0}/feectools/linalg/solvers.py +0 -0
  84. {feectools-0.1.11 → feectools-0.2.0}/feectools/linalg/sparse.py +0 -0
  85. {feectools-0.1.11 → feectools-0.2.0}/feectools/linalg/stencil.py +0 -0
  86. {feectools-0.1.11 → feectools-0.2.0}/feectools/linalg/stencil_dot_kernels.py +0 -0
  87. {feectools-0.1.11 → feectools-0.2.0}/feectools/linalg/stencil_transpose_kernels.py +0 -0
  88. {feectools-0.1.11 → feectools-0.2.0}/feectools/linalg/tests/__init__.py +0 -0
  89. {feectools-0.1.11 → feectools-0.2.0}/feectools/linalg/tests/test_block.py +0 -0
  90. {feectools-0.1.11 → feectools-0.2.0}/feectools/linalg/tests/test_fft.py +0 -0
  91. {feectools-0.1.11 → feectools-0.2.0}/feectools/linalg/tests/test_kron_stencil_matrix.py +0 -0
  92. {feectools-0.1.11 → feectools-0.2.0}/feectools/linalg/tests/test_linalg.py +0 -0
  93. {feectools-0.1.11 → feectools-0.2.0}/feectools/linalg/tests/test_matrix_free.py +0 -0
  94. {feectools-0.1.11 → feectools-0.2.0}/feectools/linalg/tests/test_solvers.py +0 -0
  95. {feectools-0.1.11 → feectools-0.2.0}/feectools/linalg/tests/test_stencil_interface_matrix.py +0 -0
  96. {feectools-0.1.11 → feectools-0.2.0}/feectools/linalg/tests/test_stencil_vector.py +0 -0
  97. {feectools-0.1.11 → feectools-0.2.0}/feectools/linalg/tests/test_stencil_vector_space.py +0 -0
  98. {feectools-0.1.11 → feectools-0.2.0}/feectools/linalg/tests/utilities.py +0 -0
  99. {feectools-0.1.11 → feectools-0.2.0}/feectools/linalg/topetsc.py +0 -0
  100. {feectools-0.1.11 → feectools-0.2.0}/feectools/linalg/utilities.py +0 -0
  101. {feectools-0.1.11 → feectools-0.2.0}/feectools/utilities/__init__.py +0 -0
  102. {feectools-0.1.11 → feectools-0.2.0}/feectools/utilities/quadratures.py +0 -0
  103. {feectools-0.1.11 → feectools-0.2.0}/feectools/utilities/utils.py +0 -0
  104. {feectools-0.1.11 → feectools-0.2.0}/feectools/version.py +0 -0
  105. {feectools-0.1.11 → feectools-0.2.0}/feectools.egg-info/dependency_links.txt +0 -0
  106. {feectools-0.1.11 → feectools-0.2.0}/feectools.egg-info/entry_points.txt +0 -0
  107. {feectools-0.1.11 → feectools-0.2.0}/feectools.egg-info/requires.txt +0 -0
  108. {feectools-0.1.11 → feectools-0.2.0}/feectools.egg-info/top_level.txt +0 -0
  109. {feectools-0.1.11 → feectools-0.2.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: feectools
3
- Version: 0.1.11
3
+ Version: 0.2.0
4
4
  Summary: Slimmed-down fork of Psydac (https://github.com/pyccel/psydac) with less functionality and fewer dependencies.
5
5
  Author-email: Psydac development team <psydac@googlegroups.com>
6
6
  Maintainer-email: Stefan Possanner <stefan.possanner@ipp.mpg.de>, Max Lindqvist <max.lindqvist@ipp.mpg.de>, Yaman Güçlü <yaman.guclu@gmail.com>, Martin Campos Pinto <martin.campos-pinto@ipp.mpg.de>, Ahmed Ratnani <ratnaniahmed@gmail.com>
@@ -7,6 +7,7 @@ provides the fundamental classes for linear algebra operations.
7
7
 
8
8
  """
9
9
 
10
+ import itertools
10
11
  from abc import ABC, abstractmethod
11
12
  from types import LambdaType
12
13
  from inspect import signature
@@ -280,13 +281,143 @@ class LinearOperator(ABC):
280
281
  upon convertion to matrix.
281
282
  """
282
283
 
283
- @abstractmethod
284
- def tosparse(self):
285
- """ Convert to a sparse matrix in any of the formats supported by scipy.sparse."""
284
+ def toarray(self, out=None, is_sparse=False, format='csr'):
285
+ """
286
+ Assemble the global matrix of the linear operator column by column.
286
287
 
287
- @abstractmethod
288
- def toarray(self):
289
- """ Convert to Numpy 2D array. """
288
+ Column j is computed as ``self.dot(e_j)``, where e_j is the j-th
289
+ canonical basis vector of the domain, in the global numbering of
290
+ ``Vector.toarray()`` (C-ordered within each StencilVector, blocks
291
+ concatenated). Since only ``dot`` is used, this works for any linear
292
+ operator, including matrix-free ones, whose domain is a
293
+ StencilVectorSpace or a (possibly nested) BlockVectorSpace thereof.
294
+
295
+ The cost is one call to ``dot`` per global degree of freedom of the
296
+ domain, hence this default is meant for testing and small problems.
297
+ Subclasses with an explicit matrix representation should override it.
298
+
299
+ In parallel, all ranks call ``dot`` collectively, each rank fills the
300
+ rows it owns, and every rank receives the full matrix.
301
+
302
+ Parameters
303
+ ----------
304
+ out : numpy.ndarray, optional
305
+ Dense array of shape ``self.shape`` into which the result is
306
+ written in place. Must be None if ``is_sparse`` is True.
307
+
308
+ is_sparse : bool
309
+ If True, return a scipy.sparse matrix, otherwise a dense
310
+ numpy.ndarray.
311
+
312
+ format : str
313
+ Sparse format, only used if ``is_sparse`` is True: one of 'csr'
314
+ (default), 'csc', 'bsr', 'lil', 'dok', 'coo' or 'dia'.
315
+
316
+ Returns
317
+ -------
318
+ numpy.ndarray or scipy.sparse matrix
319
+ The global matrix of shape ``self.shape``, identical on all ranks.
320
+ """
321
+ from feectools.linalg.block import BlockVectorSpace
322
+ from feectools.linalg.stencil import StencilVectorSpace
323
+
324
+ # Flatten the (possibly nested) block structure of the domain
325
+ def leaves(w):
326
+ if isinstance(w.space, StencilVectorSpace):
327
+ return [w]
328
+ elif isinstance(w.space, BlockVectorSpace):
329
+ return [lw for b in w.blocks for lw in leaves(b)]
330
+ else:
331
+ raise TypeError(f'{type(self).__name__}.toarray() requires a domain made of '
332
+ f'StencilVectorSpaces, not {type(w.space).__name__}.')
333
+
334
+ e_j = self.domain.zeros()
335
+ Ae_j = self.codomain.zeros()
336
+ e_j_leaves = leaves(e_j)
337
+ offsets = xp.cumsum([0] + [lw.space.dimension for lw in e_j_leaves[:-1]])
338
+
339
+ if is_sparse:
340
+ assert out is None, 'out must be None if is_sparse is True.'
341
+ assert format in ('csr', 'csc', 'bsr', 'lil', 'dok', 'coo', 'dia'), \
342
+ f'Unknown sparse format {format!r}.'
343
+ rows, cols, data = [], [], []
344
+ elif out is None:
345
+ out = xp.zeros(self.shape, dtype=self.dtype)
346
+ else:
347
+ assert isinstance(out, xp.ndarray)
348
+ assert out.shape == self.shape, f'out has shape {out.shape}, expected {self.shape}.'
349
+
350
+ # Index ranges owned by each rank, for every leaf
351
+ bounds = [(lw.starts, lw.ends) for lw in e_j_leaves]
352
+ if e_j_leaves[0].space.parallel:
353
+ comm = e_j_leaves[0].space.cart.comm
354
+ rank = comm.Get_rank()
355
+ all_bounds = comm.allgather(bounds)
356
+ else:
357
+ comm = None
358
+ rank = 0
359
+ all_bounds = [bounds]
360
+
361
+ # All ranks loop over all columns, since dot() is collective;
362
+ # only the owner of index i sets the entry of e_j to one.
363
+ for owner, owner_bounds in enumerate(all_bounds):
364
+ for lw, offset, (starts, ends) in zip(e_j_leaves, offsets, owner_bounds):
365
+ for i in itertools.product(*(range(s, e + 1) for s, e in zip(starts, ends))):
366
+ if rank == owner:
367
+ lw[i] = 1
368
+ lw.update_ghost_regions()
369
+ self.dot(e_j, out=Ae_j)
370
+ if rank == owner:
371
+ lw[i] = 0
372
+
373
+ # Global vector with nonzeros only in the rows owned by this rank
374
+ col_j = Ae_j.toarray()
375
+ j = offset + xp.ravel_multi_index(i, lw.space.npts)
376
+ if is_sparse:
377
+ nz = xp.flatnonzero(col_j)
378
+ rows.append(nz)
379
+ cols.append(xp.full(nz.size, j))
380
+ data.append(col_j[nz])
381
+ else:
382
+ out[:, j] = col_j
383
+ # Clear the ghost regions of the last entry set to one
384
+ lw.update_ghost_regions()
385
+
386
+ if not is_sparse:
387
+ if comm is not None:
388
+ from feectools.ddm.mpi import mpi as MPI
389
+ comm.Allreduce(MPI.IN_PLACE, out, op=MPI.SUM)
390
+ return out
391
+
392
+ rows = xp.concatenate(rows) if rows else xp.zeros(0, dtype=int)
393
+ cols = xp.concatenate(cols) if cols else xp.zeros(0, dtype=int)
394
+ data = xp.concatenate(data) if data else xp.zeros(0, dtype=self.dtype)
395
+ if comm is not None:
396
+ rows = xp.concatenate(comm.allgather(rows))
397
+ cols = xp.concatenate(comm.allgather(cols))
398
+ data = xp.concatenate(comm.allgather(data))
399
+
400
+ return coo_matrix((data, (rows, cols)), shape=self.shape).asformat(format)
401
+
402
+ def tosparse(self, format='csr'):
403
+ """
404
+ Assemble the global matrix of the linear operator as a scipy.sparse matrix.
405
+
406
+ Default implementation calling the generic ``LinearOperator.toarray``
407
+ with ``is_sparse=True``; see there for cost and parallel behavior.
408
+ Subclasses with an explicit matrix representation should override it.
409
+
410
+ Parameters
411
+ ----------
412
+ format : str
413
+ One of 'csr' (default), 'csc', 'bsr', 'lil', 'dok', 'coo' or 'dia'.
414
+
415
+ Returns
416
+ -------
417
+ scipy.sparse matrix
418
+ The global matrix of shape ``self.shape``, identical on all ranks.
419
+ """
420
+ return LinearOperator.toarray(self, is_sparse=True, format=format)
290
421
 
291
422
  @abstractmethod
292
423
  def dot(self, v, out=None):
@@ -977,9 +1108,6 @@ class ComposedLinearOperator(LinearOperator):
977
1108
  def dtype(self):
978
1109
  return None
979
1110
 
980
- def toarray(self):
981
- raise NotImplementedError('toarray() is not defined for ComposedLinearOperators.')
982
-
983
1111
  def tosparse(self):
984
1112
  mats = [M.tosparse() for M in self._multiplicants]
985
1113
  M = mats[0]
@@ -1084,12 +1212,6 @@ class PowerLinearOperator(LinearOperator):
1084
1212
  """ Returns the power to which the operator is raised. """
1085
1213
  return self._factorial
1086
1214
 
1087
- def toarray(self):
1088
- raise NotImplementedError('toarray() is not defined for PowerLinearOperators.')
1089
-
1090
- def tosparse(self):
1091
- raise NotImplementedError('tosparse() is not defined for PowerLinearOperators.')
1092
-
1093
1215
  def transpose(self, conjugate=False):
1094
1216
  return PowerLinearOperator(domain=self.codomain, codomain=self.domain, A=self._operator.transpose(conjugate=conjugate), n=self._factorial)
1095
1217
 
@@ -1207,12 +1329,6 @@ class InverseLinearOperator(LinearOperator):
1207
1329
  elif key == 'verbose':
1208
1330
  assert isinstance(value, bool), "verbose must be a bool"
1209
1331
 
1210
- def toarray(self):
1211
- raise NotImplementedError('toarray() is not defined for InverseLinearOperators.')
1212
-
1213
- def tosparse(self):
1214
- raise NotImplementedError('tosparse() is not defined for InverseLinearOperators.')
1215
-
1216
1332
  def get_info(self):
1217
1333
  """ Returns the previous convergence information. """
1218
1334
  return self._info
@@ -1366,12 +1482,6 @@ class MatrixFreeLinearOperator(LinearOperator):
1366
1482
 
1367
1483
  return out
1368
1484
 
1369
- def toarray(self):
1370
- raise NotImplementedError('toarray() is not defined for MatrixFreeLinearOperator.')
1371
-
1372
- def tosparse(self):
1373
- raise NotImplementedError('tosparse() is not defined for MatrixFreeLinearOperator.')
1374
-
1375
1485
  def transpose(self, conjugate=False):
1376
1486
  if self._dot_transpose is None:
1377
1487
  raise NotImplementedError('no transpose dot method was given -- cannot create the transpose operator')
@@ -21,12 +21,6 @@ class DistributedFFTBase(LinearOperator):
21
21
  The function at position i is applied to the i-th tensor direction.
22
22
  If only a single callable is given, it is used for all directions.
23
23
  """
24
- def toarray(self):
25
- raise NotImplementedError('toarray() is not defined for DistributedFFTBase.')
26
-
27
- def tosparse(self):
28
- raise NotImplementedError('tosparse() is not defined for DistributedFFTBase.')
29
-
30
24
  # Possible additions for the future:
31
25
  # * split off the LinearSolver class when used with the space ndarray (as used in the KroneckerLinearSolver),
32
26
  # and make it state if it works in-place (or if it needs temporary memory), and what its optimal
@@ -528,12 +528,6 @@ class KroneckerLinearSolver(LinearOperator):
528
528
  @property
529
529
  def dtype(self):
530
530
  return None
531
-
532
- def toarray(self):
533
- raise NotImplementedError('toarray() is not defined for KroneckerLinearSolvers.')
534
-
535
- def tosparse(self):
536
- raise NotImplementedError('tosparse() is not defined for KroneckerLinearSolvers.')
537
531
 
538
532
  def transpose(self, conjugate=False):
539
533
  new_domain = self._codomain
@@ -0,0 +1,162 @@
1
+ # -*- coding: UTF-8 -*-
2
+ #
3
+ # Tests for the generic LinearOperator.toarray() and LinearOperator.tosparse(),
4
+ # which assemble the matrix of any linear operator from its dot() method.
5
+ #
6
+ import pytest
7
+ import cunumpy as xp
8
+
9
+ from feectools.ddm.mpi import mpi as MPI
10
+ from feectools.ddm.cart import DomainDecomposition, CartDecomposition
11
+ from feectools.linalg.basic import LinearOperator, MatrixFreeLinearOperator, IdentityOperator
12
+ from feectools.linalg.stencil import StencilVectorSpace, StencilMatrix
13
+ from feectools.linalg.block import BlockVectorSpace, BlockLinearOperator
14
+ from feectools.linalg.solvers import inverse
15
+ from feectools.linalg.tests.test_block import compute_global_starts_ends
16
+
17
+ SPARSE_FORMATS = ['csr', 'csc', 'bsr', 'lil', 'dok', 'coo', 'dia']
18
+
19
+ #===============================================================================
20
+ # HELPERS
21
+ #===============================================================================
22
+ def get_space(npts, pads, periods, comm=None):
23
+ D = DomainDecomposition(npts, periods=periods, comm=comm)
24
+ global_starts, global_ends = compute_global_starts_ends(D, npts)
25
+ C = CartDecomposition(D, npts, global_starts, global_ends, pads=pads, shifts=[1] * len(npts))
26
+ return StencilVectorSpace(C)
27
+
28
+ def get_random_matrix(V, W, seed):
29
+ """ Random StencilMatrix, identical on all ranks in the global numbering. """
30
+ rng = xp.random.default_rng(seed)
31
+ M = StencilMatrix(V, W)
32
+ # Draw the full (global) band and keep the local part, so the matrix
33
+ # does not depend on the domain decomposition.
34
+ shape = tuple(W.npts) + M._data.shape[W.ndim:]
35
+ band = rng.random(shape) - 0.5
36
+ idx = tuple(slice(s, e + 1) for s, e in zip(W.starts, W.ends))
37
+ M[idx] = band[idx]
38
+ M.remove_spurious_entries()
39
+ return M
40
+
41
+ def matrix_free(A):
42
+ """ Hide the explicit matrix of A, so that the generic toarray() is used. """
43
+ return MatrixFreeLinearOperator(A.domain, A.codomain, lambda v, out=None: A.dot(v, out=out))
44
+
45
+ def reference(A):
46
+ """ Global dense matrix of A, from its own (row-local in parallel) tosparse(). """
47
+ local = A.tosparse().toarray()
48
+ comm = A.domain.cart.comm if isinstance(A.domain, StencilVectorSpace) else A.domain.spaces[0].cart.comm
49
+ if A.domain.parallel:
50
+ glob = xp.zeros_like(local)
51
+ comm.Allreduce(local, glob, op=MPI.SUM)
52
+ return glob
53
+ return local
54
+
55
+ def check_all(O, ref):
56
+ """ Check dense, in-place and all sparse outputs of the generic toarray()/tosparse(). """
57
+ assert xp.allclose(O.toarray(), ref)
58
+
59
+ out = xp.full(O.shape, 7.0)
60
+ res = O.toarray(out=out)
61
+ assert res is out
62
+ assert xp.allclose(out, ref)
63
+
64
+ for fmt in SPARSE_FORMATS:
65
+ S = O.toarray(is_sparse=True, format=fmt)
66
+ assert S.format == fmt
67
+ assert S.shape == O.shape
68
+ assert xp.allclose(S.toarray(), ref)
69
+
70
+ S = O.tosparse()
71
+ assert S.format == 'csr'
72
+ assert xp.allclose(S.toarray(), ref)
73
+ assert O.tosparse('csc').format == 'csc'
74
+
75
+ def stencil_case(periods, comm):
76
+ V = get_space([6, 5], [2, 1], periods, comm=comm)
77
+ return get_random_matrix(V, V, seed=0)
78
+
79
+ def block_case(periods, comm):
80
+ """ 2x2 block operator, and a nested block operator [[B, 0], [0, A]]. """
81
+ V = get_space([6, 5], [2, 1], periods, comm=comm)
82
+ A = get_random_matrix(V, V, seed=0)
83
+ A01 = get_random_matrix(V, V, seed=1)
84
+ VV = BlockVectorSpace(V, V)
85
+ B = BlockLinearOperator(VV, VV, blocks=[[A, A01], [None, A]])
86
+ Vn = BlockVectorSpace(VV, V)
87
+ N = BlockLinearOperator(Vn, Vn, blocks=[[B, None], [None, A]])
88
+ return A, B, N
89
+
90
+ #===============================================================================
91
+ # SERIAL TESTS
92
+ #===============================================================================
93
+ @pytest.mark.parametrize('periods', [[False, False], [True, False], [True, True]])
94
+ def test_toarray_stencil(periods):
95
+ A = stencil_case(periods, comm=None)
96
+ check_all(matrix_free(A), reference(A))
97
+
98
+ @pytest.mark.parametrize('periods', [[False, False], [True, True]])
99
+ def test_toarray_block(periods):
100
+ A, B, N = block_case(periods, comm=None)
101
+ refA, refB = reference(A), reference(B)
102
+ check_all(matrix_free(B), refB)
103
+
104
+ Z = xp.zeros((refB.shape[0], refA.shape[1]))
105
+ refN = xp.block([[refB, Z], [Z.T, refA]])
106
+ check_all(matrix_free(N), refN)
107
+
108
+ def test_toarray_composite_operators():
109
+ """ Operators that used to raise NotImplementedError in toarray()/tosparse(). """
110
+ A = stencil_case([True, False], comm=None)
111
+ ref = reference(A)
112
+ V = A.domain
113
+
114
+ assert xp.allclose((A @ A).toarray(), ref @ ref)
115
+ assert xp.allclose((A ** 2).toarray(), ref @ ref)
116
+ assert xp.allclose((A ** 2).tosparse().toarray(), ref @ ref)
117
+
118
+ M = A.T @ A + IdentityOperator(V)
119
+ refM = ref.T @ ref + xp.eye(ref.shape[0])
120
+ Minv = inverse(M, 'cg', tol=1e-13, maxiter=1000)
121
+ assert xp.allclose(Minv.toarray(), xp.linalg.inv(refM), atol=1e-8)
122
+
123
+ def test_toarray_invalid_input():
124
+ O = matrix_free(stencil_case([False, False], comm=None))
125
+ with pytest.raises(AssertionError):
126
+ O.toarray(out=xp.zeros((O.shape[0], O.shape[1] + 1)))
127
+ with pytest.raises(AssertionError):
128
+ O.toarray(out=xp.zeros(O.shape), is_sparse=True)
129
+ with pytest.raises(AssertionError):
130
+ O.toarray(is_sparse=True, format='xyz')
131
+
132
+ #===============================================================================
133
+ # PARALLEL TESTS
134
+ #===============================================================================
135
+ @pytest.mark.parametrize('periods', [[False, False], [True, False], [True, True]])
136
+ @pytest.mark.parallel
137
+ def test_toarray_stencil_parallel(periods):
138
+ A = stencil_case(periods, comm=MPI.COMM_WORLD)
139
+ check_all(matrix_free(A), reference(A))
140
+
141
+ @pytest.mark.parametrize('periods', [[False, False], [True, True]])
142
+ @pytest.mark.parallel
143
+ def test_toarray_block_parallel(periods):
144
+ A, B, N = block_case(periods, comm=MPI.COMM_WORLD)
145
+ refA, refB = reference(A), reference(B)
146
+ check_all(matrix_free(B), refB)
147
+
148
+ Z = xp.zeros((refB.shape[0], refA.shape[1]))
149
+ refN = xp.block([[refB, Z], [Z.T, refA]])
150
+ check_all(matrix_free(N), refN)
151
+
152
+ @pytest.mark.parallel
153
+ def test_toarray_composite_operators_parallel():
154
+ A = stencil_case([True, False], comm=MPI.COMM_WORLD)
155
+ ref = reference(A)
156
+ assert xp.allclose((A @ A).toarray(), ref @ ref)
157
+ assert xp.allclose((A ** 2).tosparse().toarray(), ref @ ref)
158
+
159
+ #===============================================================================
160
+ if __name__ == '__main__':
161
+ import sys
162
+ pytest.main(sys.argv)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: feectools
3
- Version: 0.1.11
3
+ Version: 0.2.0
4
4
  Summary: Slimmed-down fork of Psydac (https://github.com/pyccel/psydac) with less functionality and fewer dependencies.
5
5
  Author-email: Psydac development team <psydac@googlegroups.com>
6
6
  Maintainer-email: Stefan Possanner <stefan.possanner@ipp.mpg.de>, Max Lindqvist <max.lindqvist@ipp.mpg.de>, Yaman Güçlü <yaman.guclu@gmail.com>, Martin Campos Pinto <martin.campos-pinto@ipp.mpg.de>, Ahmed Ratnani <ratnaniahmed@gmail.com>
@@ -100,6 +100,7 @@ feectools/linalg/tests/test_solvers.py
100
100
  feectools/linalg/tests/test_stencil_interface_matrix.py
101
101
  feectools/linalg/tests/test_stencil_vector.py
102
102
  feectools/linalg/tests/test_stencil_vector_space.py
103
+ feectools/linalg/tests/test_toarray.py
103
104
  feectools/linalg/tests/utilities.py
104
105
  feectools/utilities/__init__.py
105
106
  feectools/utilities/quadratures.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "feectools"
7
- version = "0.1.11"
7
+ version = "0.2.0"
8
8
  description = "Slimmed-down fork of Psydac (https://github.com/pyccel/psydac) with less functionality and fewer dependencies."
9
9
  readme = "README.md"
10
10
  requires-python = ">= 3.10"
File without changes
File without changes
File without changes
File without changes