feectools 0.4.0__tar.gz → 0.6.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 (162) hide show
  1. {feectools-0.4.0/feectools.egg-info → feectools-0.6.0}/PKG-INFO +2 -1
  2. feectools-0.6.0/feectools/__init__.py +7 -0
  3. {feectools-0.4.0 → feectools-0.6.0}/feectools/ddm/blocking_data_exchanger.py +1 -2
  4. {feectools-0.4.0 → feectools-0.6.0}/feectools/ddm/cart.py +5 -13
  5. {feectools-0.4.0 → feectools-0.6.0}/feectools/ddm/interface_data_exchanger.py +1 -2
  6. {feectools-0.4.0 → feectools-0.6.0}/feectools/ddm/nonblocking_data_exchanger.py +1 -1
  7. {feectools-0.4.0 → feectools-0.6.0}/feectools/ddm/tests/test_cart_1d.py +1 -1
  8. {feectools-0.4.0 → feectools-0.6.0}/feectools/ddm/tests/test_cart_2d.py +1 -1
  9. {feectools-0.4.0 → feectools-0.6.0}/feectools/ddm/tests/test_cart_3d.py +1 -1
  10. {feectools-0.4.0 → feectools-0.6.0}/feectools/ddm/tests/test_coarsen.py +1 -3
  11. {feectools-0.4.0 → feectools-0.6.0}/feectools/ddm/tests/test_multicart_2d.py +1 -1
  12. {feectools-0.4.0 → feectools-0.6.0}/feectools/fem/tensor.py +1 -2
  13. {feectools-0.4.0 → feectools-0.6.0}/feectools/fem/tests/test_spline_interpolation.py +1 -2
  14. {feectools-0.4.0 → feectools-0.6.0}/feectools/fem/tests/test_splines_par.py +1 -2
  15. {feectools-0.4.0 → feectools-0.6.0}/feectools/fem/tests/test_tensor.py +1 -1
  16. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/basic.py +4 -2
  17. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/block.py +6 -3
  18. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/direct_solvers.py +110 -22
  19. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/fft.py +3 -0
  20. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/kernels/stencil_dot_1d/stencil_dot_1d_cuda.cu +10 -7
  21. feectools-0.6.0/feectools/linalg/kernels/stencil_dot_1d/stencil_dot_1d_kernels.py +38 -0
  22. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/kernels/stencil_dot_2d/stencil_dot_2d_cuda.cu +12 -9
  23. feectools-0.6.0/feectools/linalg/kernels/stencil_dot_2d/stencil_dot_2d_kernels.py +47 -0
  24. feectools-0.6.0/feectools/linalg/kernels/stencil_dot_3d/stencil_dot_3d_cuda.cu +52 -0
  25. feectools-0.6.0/feectools/linalg/kernels/stencil_dot_3d/stencil_dot_3d_kernels.py +63 -0
  26. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/kernels/stencil_transpose_1d/stencil_transpose_1d_cuda.cu +12 -12
  27. feectools-0.6.0/feectools/linalg/kernels/stencil_transpose_1d/stencil_transpose_1d_kernels.py +38 -0
  28. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/kernels/stencil_transpose_2d/stencil_transpose_2d_cuda.cu +11 -12
  29. feectools-0.6.0/feectools/linalg/kernels/stencil_transpose_2d/stencil_transpose_2d_kernels.py +52 -0
  30. feectools-0.6.0/feectools/linalg/kernels/stencil_transpose_3d/stencil_transpose_3d_cuda.cu +57 -0
  31. feectools-0.6.0/feectools/linalg/kernels/stencil_transpose_3d/stencil_transpose_3d_kernels.py +69 -0
  32. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/kron.py +45 -0
  33. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/solvers.py +62 -32
  34. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/stencil.py +49 -28
  35. feectools-0.6.0/feectools/linalg/tests/inner_on_device_child.py +149 -0
  36. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/tests/kernel_test_args.py +34 -17
  37. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/tests/test_block.py +1 -1
  38. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/tests/test_cuda_parity.py +1 -1
  39. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/tests/test_device_matvec.py +63 -20
  40. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/tests/test_fft.py +1 -1
  41. feectools-0.6.0/feectools/linalg/tests/test_inner_on_device.py +160 -0
  42. feectools-0.6.0/feectools/linalg/tests/test_kron_device_solve.py +284 -0
  43. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/tests/test_mpi_device.py +63 -1
  44. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/tests/test_stencil_interface_matrix.py +1 -1
  45. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/tests/test_stencil_vector.py +1 -1
  46. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/tests/test_stencil_vector_space.py +1 -1
  47. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/tests/test_toarray.py +1 -1
  48. {feectools-0.4.0 → feectools-0.6.0/feectools.egg-info}/PKG-INFO +2 -1
  49. {feectools-0.4.0 → feectools-0.6.0}/feectools.egg-info/SOURCES.txt +3 -1
  50. {feectools-0.4.0 → feectools-0.6.0}/feectools.egg-info/requires.txt +1 -0
  51. {feectools-0.4.0 → feectools-0.6.0}/pyproject.toml +2 -1
  52. feectools-0.4.0/feectools/__init__.py +0 -4
  53. feectools-0.4.0/feectools/ddm/mpi.py +0 -223
  54. feectools-0.4.0/feectools/linalg/kernels/stencil_dot_1d/stencil_dot_1d_kernels.py +0 -35
  55. feectools-0.4.0/feectools/linalg/kernels/stencil_dot_2d/stencil_dot_2d_kernels.py +0 -99
  56. feectools-0.4.0/feectools/linalg/kernels/stencil_dot_3d/stencil_dot_3d_cuda.cu +0 -64
  57. feectools-0.4.0/feectools/linalg/kernels/stencil_dot_3d/stencil_dot_3d_kernels.py +0 -206
  58. feectools-0.4.0/feectools/linalg/kernels/stencil_transpose_1d/stencil_transpose_1d_kernels.py +0 -37
  59. feectools-0.4.0/feectools/linalg/kernels/stencil_transpose_2d/stencil_transpose_2d_kernels.py +0 -116
  60. feectools-0.4.0/feectools/linalg/kernels/stencil_transpose_3d/stencil_transpose_3d_cuda.cu +0 -76
  61. feectools-0.4.0/feectools/linalg/kernels/stencil_transpose_3d/stencil_transpose_3d_kernels.py +0 -252
  62. {feectools-0.4.0 → feectools-0.6.0}/AUTHORS +0 -0
  63. {feectools-0.4.0 → feectools-0.6.0}/LICENSE +0 -0
  64. {feectools-0.4.0 → feectools-0.6.0}/README.md +0 -0
  65. {feectools-0.4.0 → feectools-0.6.0}/feectools/accelerate/__init__.py +0 -0
  66. {feectools-0.4.0 → feectools-0.6.0}/feectools/accelerate/accelerate.py +0 -0
  67. {feectools-0.4.0 → feectools-0.6.0}/feectools/accelerate/compile_psydac.mk +0 -0
  68. {feectools-0.4.0 → feectools-0.6.0}/feectools/api/__init__.py +0 -0
  69. {feectools-0.4.0 → feectools-0.6.0}/feectools/api/essential_bc.py +0 -0
  70. {feectools-0.4.0 → feectools-0.6.0}/feectools/api/fem_bilinear_form.py +0 -0
  71. {feectools-0.4.0 → feectools-0.6.0}/feectools/api/fem_common.py +0 -0
  72. {feectools-0.4.0 → feectools-0.6.0}/feectools/api/fem_sum_form.py +0 -0
  73. {feectools-0.4.0 → feectools-0.6.0}/feectools/api/settings.py +0 -0
  74. {feectools-0.4.0 → feectools-0.6.0}/feectools/core/__init__.py +0 -0
  75. {feectools-0.4.0 → feectools-0.6.0}/feectools/core/bsplines.py +0 -0
  76. {feectools-0.4.0 → feectools-0.6.0}/feectools/core/bsplines_kernels.py +0 -0
  77. {feectools-0.4.0 → feectools-0.6.0}/feectools/core/field_evaluation_kernels.py +0 -0
  78. {feectools-0.4.0 → feectools-0.6.0}/feectools/core/tests/__init__.py +0 -0
  79. {feectools-0.4.0 → feectools-0.6.0}/feectools/core/tests/test_bsplines.py +0 -0
  80. {feectools-0.4.0 → feectools-0.6.0}/feectools/core/tests/test_bsplines_kernel.py +0 -0
  81. {feectools-0.4.0 → feectools-0.6.0}/feectools/core/tests/test_bsplines_pyccel.py +0 -0
  82. {feectools-0.4.0 → feectools-0.6.0}/feectools/ddm/__init__.py +0 -0
  83. {feectools-0.4.0 → feectools-0.6.0}/feectools/ddm/basic.py +0 -0
  84. {feectools-0.4.0 → feectools-0.6.0}/feectools/ddm/partition.py +0 -0
  85. {feectools-0.4.0 → feectools-0.6.0}/feectools/ddm/petsc.py +0 -0
  86. {feectools-0.4.0 → feectools-0.6.0}/feectools/ddm/tests/__init__.py +0 -0
  87. {feectools-0.4.0 → feectools-0.6.0}/feectools/ddm/tests/test_device_binding.py +0 -0
  88. {feectools-0.4.0 → feectools-0.6.0}/feectools/ddm/tests/test_partition.py +0 -0
  89. {feectools-0.4.0 → feectools-0.6.0}/feectools/ddm/utilities.py +0 -0
  90. {feectools-0.4.0 → feectools-0.6.0}/feectools/feec/__init__.py +0 -0
  91. {feectools-0.4.0 → feectools-0.6.0}/feectools/feec/derivatives.py +0 -0
  92. {feectools-0.4.0 → feectools-0.6.0}/feectools/feec/dof_kernels.py +0 -0
  93. {feectools-0.4.0 → feectools-0.6.0}/feectools/feec/global_geometric_projectors.py +0 -0
  94. {feectools-0.4.0 → feectools-0.6.0}/feectools/feec/hodge.py +0 -0
  95. {feectools-0.4.0 → feectools-0.6.0}/feectools/fem/__init__.py +0 -0
  96. {feectools-0.4.0 → feectools-0.6.0}/feectools/fem/basic.py +0 -0
  97. {feectools-0.4.0 → feectools-0.6.0}/feectools/fem/grid.py +0 -0
  98. {feectools-0.4.0 → feectools-0.6.0}/feectools/fem/lst_preconditioner.py +0 -0
  99. {feectools-0.4.0 → feectools-0.6.0}/feectools/fem/partitioning.py +0 -0
  100. {feectools-0.4.0 → feectools-0.6.0}/feectools/fem/projectors.py +0 -0
  101. {feectools-0.4.0 → feectools-0.6.0}/feectools/fem/splines.py +0 -0
  102. {feectools-0.4.0 → feectools-0.6.0}/feectools/fem/tests/__init__.py +0 -0
  103. {feectools-0.4.0 → feectools-0.6.0}/feectools/fem/tests/analytical_profiles_1d.py +0 -0
  104. {feectools-0.4.0 → feectools-0.6.0}/feectools/fem/tests/analytical_profiles_base.py +0 -0
  105. {feectools-0.4.0 → feectools-0.6.0}/feectools/fem/tests/splines_error_bounds.py +0 -0
  106. {feectools-0.4.0 → feectools-0.6.0}/feectools/fem/tests/test_dirichlet_projectors.py +0 -0
  107. {feectools-0.4.0 → feectools-0.6.0}/feectools/fem/tests/test_spline_histopolation.py +0 -0
  108. {feectools-0.4.0 → feectools-0.6.0}/feectools/fem/tests/test_splines.py +0 -0
  109. {feectools-0.4.0 → feectools-0.6.0}/feectools/fem/tests/test_vector_spaces.py +0 -0
  110. {feectools-0.4.0 → feectools-0.6.0}/feectools/fem/tests/utilities.py +0 -0
  111. {feectools-0.4.0 → feectools-0.6.0}/feectools/fem/vector.py +0 -0
  112. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/__init__.py +0 -0
  113. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/kernels/__init__.py +0 -0
  114. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/kernels/matvec_kernels.py +0 -0
  115. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/kernels/stencil2IJV_kernels.py +0 -0
  116. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/kernels/stencil2coo_kernels.py +0 -0
  117. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/kernels/stencil_axpy_1d/__init__.py +0 -0
  118. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/kernels/stencil_axpy_1d/stencil_axpy_1d_cuda.cu +0 -0
  119. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/kernels/stencil_axpy_1d/stencil_axpy_1d_kernels.py +0 -0
  120. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/kernels/stencil_axpy_2d/__init__.py +0 -0
  121. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/kernels/stencil_axpy_2d/stencil_axpy_2d_cuda.cu +0 -0
  122. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/kernels/stencil_axpy_2d/stencil_axpy_2d_kernels.py +0 -0
  123. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/kernels/stencil_axpy_3d/__init__.py +0 -0
  124. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/kernels/stencil_axpy_3d/stencil_axpy_3d_cuda.cu +0 -0
  125. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/kernels/stencil_axpy_3d/stencil_axpy_3d_kernels.py +0 -0
  126. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/kernels/stencil_dot_1d/__init__.py +0 -0
  127. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/kernels/stencil_dot_2d/__init__.py +0 -0
  128. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/kernels/stencil_dot_3d/__init__.py +0 -0
  129. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/kernels/stencil_inner_1d/__init__.py +0 -0
  130. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/kernels/stencil_inner_1d/stencil_inner_1d_cuda.cu +0 -0
  131. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/kernels/stencil_inner_1d/stencil_inner_1d_kernels.py +0 -0
  132. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/kernels/stencil_inner_2d/__init__.py +0 -0
  133. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/kernels/stencil_inner_2d/stencil_inner_2d_cuda.cu +0 -0
  134. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/kernels/stencil_inner_2d/stencil_inner_2d_kernels.py +0 -0
  135. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/kernels/stencil_inner_3d/__init__.py +0 -0
  136. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/kernels/stencil_inner_3d/stencil_inner_3d_cuda.cu +0 -0
  137. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/kernels/stencil_inner_3d/stencil_inner_3d_kernels.py +0 -0
  138. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/kernels/stencil_transpose_1d/__init__.py +0 -0
  139. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/kernels/stencil_transpose_2d/__init__.py +0 -0
  140. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/kernels/stencil_transpose_3d/__init__.py +0 -0
  141. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/kernels/transpose_kernels.py +0 -0
  142. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/memory.py +0 -0
  143. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/sparse.py +0 -0
  144. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/tests/__init__.py +0 -0
  145. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/tests/cuda_parity_cases.py +0 -0
  146. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/tests/test_axpy_ghost_sync.py +0 -0
  147. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/tests/test_cuda_emulation.py +0 -0
  148. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/tests/test_kron_stencil_matrix.py +0 -0
  149. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/tests/test_linalg.py +0 -0
  150. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/tests/test_matrix_free.py +0 -0
  151. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/tests/test_solvers.py +0 -0
  152. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/tests/utilities.py +0 -0
  153. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/topetsc.py +0 -0
  154. {feectools-0.4.0 → feectools-0.6.0}/feectools/linalg/utilities.py +0 -0
  155. {feectools-0.4.0 → feectools-0.6.0}/feectools/utilities/__init__.py +0 -0
  156. {feectools-0.4.0 → feectools-0.6.0}/feectools/utilities/quadratures.py +0 -0
  157. {feectools-0.4.0 → feectools-0.6.0}/feectools/utilities/utils.py +0 -0
  158. {feectools-0.4.0 → feectools-0.6.0}/feectools/version.py +0 -0
  159. {feectools-0.4.0 → feectools-0.6.0}/feectools.egg-info/dependency_links.txt +0 -0
  160. {feectools-0.4.0 → feectools-0.6.0}/feectools.egg-info/entry_points.txt +0 -0
  161. {feectools-0.4.0 → feectools-0.6.0}/feectools.egg-info/top_level.txt +0 -0
  162. {feectools-0.4.0 → feectools-0.6.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: feectools
3
- Version: 0.4.0
3
+ Version: 0.6.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>
@@ -42,6 +42,7 @@ Requires-Dist: matplotlib
42
42
  Requires-Dist: pyyaml>=5.1
43
43
  Requires-Dist: packaging
44
44
  Requires-Dist: pyevtk
45
+ Requires-Dist: maybempi>=0.1.2
45
46
  Requires-Dist: cunumpy>=0.6.1
46
47
  Requires-Dist: pyccel>=2.1.0
47
48
  Requires-Dist: h5py
@@ -0,0 +1,7 @@
1
+ # Bind this rank to its own GPU (by its rank within the node) and create the CUDA context before MPI starts, as
2
+ # CUDA-aware MPI requires. Every feectools module imports this package first, and MPI starts only when a module
3
+ # imports `from maybempi import MPI` (maybempi uses mpi4py under an MPI launcher and a serial stand-in otherwise;
4
+ # MAYBEMPI=0/1 overrides the detection). A no-op on the NumPy backend.
5
+ from cunumpy.cuda import bind_local_device
6
+
7
+ bind_local_device()
@@ -3,8 +3,7 @@
3
3
  import cunumpy as xp
4
4
  from cunumpy.mpi import synchronize_for_mpi
5
5
  import numpy as np
6
- from feectools.ddm.mpi import mpi as MPI
7
-
6
+ from maybempi import MPI
8
7
  from .cart import CartDecomposition, find_mpi_type
9
8
  from .basic import CartDataExchanger
10
9
 
@@ -10,16 +10,8 @@ from itertools import product
10
10
 
11
11
  from cunumpy.xp import array_backend, to_numpy
12
12
 
13
- # Bind this rank to its own GPU (by its rank within the node) and create the
14
- # CUDA context before MPI is initialized, as CUDA-aware MPI requires. Must stay
15
- # above the feectools.ddm.mpi import, which initializes MPI as a side effect.
16
- # A no-op on the NumPy backend.
17
- from cunumpy.cuda import bind_local_device
18
-
19
- bind_local_device()
20
-
21
- from feectools.ddm.mpi import mpi as MPI
22
- from feectools.ddm.mpi import MockMPI
13
+ # The GPU of this rank is bound in feectools/__init__.py, before MPI starts.
14
+ from maybempi import MPI, is_serial
23
15
  from feectools.ddm.partition import compute_dims, partition_procs_per_patch
24
16
 
25
17
 
@@ -46,7 +38,7 @@ def find_mpi_type( dtype ):
46
38
  MPI datatype to be used for communication.
47
39
 
48
40
  """
49
- if not isinstance(MPI, MockMPI):
41
+ if not is_serial(MPI):
50
42
  if isinstance( dtype, MPI.Datatype ):
51
43
  mpi_type = dtype
52
44
  else:
@@ -92,7 +84,7 @@ class MultiPatchDomainDecomposition:
92
84
  def __init__(self, ncells, periods, comm=None, num_threads=None):
93
85
 
94
86
  assert len( ncells ) == len( periods )
95
- if not isinstance(MPI, MockMPI) and comm is not None:
87
+ if not is_serial(MPI) and comm is not None:
96
88
  assert isinstance( comm, MPI.Comm )
97
89
  num_threads = num_threads if num_threads else int(os.environ.get('OMP_NUM_THREADS', 1))
98
90
 
@@ -236,7 +228,7 @@ class DomainDecomposition:
236
228
  assert len( ncells ) == len( periods )
237
229
  assert all( n >=1 for n in ncells )
238
230
  assert all( isinstance( period, bool ) for period in periods )
239
- if isinstance(MPI, MockMPI):
231
+ if is_serial(MPI):
240
232
  comm = None
241
233
  else:
242
234
  if comm is not None:
@@ -1,8 +1,7 @@
1
1
  # coding: utf-8
2
2
 
3
3
  from cunumpy.mpi import synchronize_for_mpi
4
- from feectools.ddm.mpi import mpi as MPI
5
-
4
+ from maybempi import MPI
6
5
  from .cart import InterfaceCartDecomposition, find_mpi_type
7
6
 
8
7
  __all__ = ('InterfaceCartDataExchanger',)
@@ -5,7 +5,7 @@ from cunumpy.mpi import synchronize_for_mpi
5
5
  import numpy as np
6
6
  from itertools import product
7
7
 
8
- from feectools.ddm.mpi import mpi as MPI
8
+ from maybempi import MPI
9
9
  from .cart import CartDecomposition, find_mpi_type
10
10
  from .basic import CartDataExchanger
11
11
 
@@ -14,7 +14,7 @@ from feectools.ddm.nonblocking_data_exchanger import NonBlockingCartDataExchange
14
14
  def run_cart_1d( data_exchanger_type, verbose=False ):
15
15
 
16
16
  import cunumpy as xp
17
- from feectools.ddm.mpi import mpi as MPI
17
+ from maybempi import MPI
18
18
  from feectools.ddm.cart import DomainDecomposition, CartDecomposition
19
19
 
20
20
  #---------------------------------------------------------------------------
@@ -9,7 +9,7 @@ from feectools.ddm.nonblocking_data_exchanger import NonBlockingCartDataExchange
9
9
  def run_cart_2d( data_exchanger_type, verbose=False , nprocs=None, reverse_axis=None):
10
10
 
11
11
  import cunumpy as xp
12
- from feectools.ddm.mpi import mpi as MPI
12
+ from maybempi import MPI
13
13
  from feectools.ddm.cart import DomainDecomposition, CartDecomposition
14
14
 
15
15
  #---------------------------------------------------------------------------
@@ -9,7 +9,7 @@ from feectools.ddm.nonblocking_data_exchanger import NonBlockingCartDataExchange
9
9
  def run_cart_3d( data_exchanger_type, verbose=False ):
10
10
 
11
11
  import cunumpy as xp
12
- from feectools.ddm.mpi import mpi as MPI
12
+ from maybempi import MPI
13
13
  from feectools.ddm.cart import DomainDecomposition, CartDecomposition
14
14
 
15
15
  #---------------------------------------------------------------------------
@@ -2,9 +2,7 @@ import numpy as np
2
2
  import pytest
3
3
 
4
4
  from feectools.ddm.cart import DomainDecomposition
5
- from feectools.ddm.mpi import mpi as MPI
6
-
7
-
5
+ from maybempi import MPI
8
6
  def _comm(parallel):
9
7
  return MPI.COMM_WORLD if parallel else None
10
8
 
@@ -41,7 +41,7 @@ def get_plus_starts_ends(minus_starts, minus_ends, minus_npts, plus_npts, minus_
41
41
  def run_carts_2d():
42
42
  import cunumpy as xp
43
43
 
44
- from feectools.ddm.mpi import mpi as MPI
44
+ from maybempi import MPI
45
45
  from feectools.ddm.cart import MultiPatchDomainDecomposition, CartDecomposition, create_interfaces_cart
46
46
  from feectools.ddm.blocking_data_exchanger import BlockingCartDataExchanger
47
47
  from feectools.ddm.interface_data_exchanger import InterfaceCartDataExchanger
@@ -5,8 +5,7 @@ We assume here that a tensor space is the product of fem spaces whom basis are
5
5
  of compact support
6
6
 
7
7
  """
8
- from feectools.ddm.mpi import mpi as MPI
9
-
8
+ from maybempi import MPI
10
9
  import cunumpy as xp
11
10
  import numpy as np
12
11
  import itertools
@@ -5,8 +5,7 @@
5
5
  #---------------------------------------------------------------------------#
6
6
  import time
7
7
 
8
- from feectools.ddm.mpi import mpi as MPI
9
-
8
+ from maybempi import MPI
10
9
  import cunumpy as xp
11
10
  import pytest
12
11
 
@@ -7,8 +7,7 @@ from feectools.fem.splines import SplineSpace
7
7
  from feectools.fem.tensor import TensorFemSpace
8
8
  from feectools.fem.vector import VectorFemSpace
9
9
  from feectools.ddm.cart import DomainDecomposition
10
- from feectools.ddm.mpi import mpi as MPI
11
-
10
+ from maybempi import MPI
12
11
  import cunumpy as xp
13
12
 
14
13
  def test_2d_1():
@@ -19,7 +19,7 @@ import numpy as np
19
19
  import matplotlib as mpl
20
20
  import matplotlib.pyplot as plt
21
21
  from PIL import Image
22
- from mpi4py import MPI
22
+ from maybempi import MPI
23
23
 
24
24
  from sympde.topology.domain import Square
25
25
  from sympde.topology.space import ScalarFunctionSpace
@@ -93,9 +93,11 @@ class VectorSpace(ABC):
93
93
 
94
94
  Returns
95
95
  -------
96
- float | complex
96
+ float | complex | cupy.ndarray
97
97
  The scalar product of the two vectors. Note that inner(x, x) is
98
98
  a non-negative real number which is zero if and only if x = 0.
99
+ For vectors with device (CuPy) data, a 0-d device array: the
100
+ result stays on the device.
99
101
 
100
102
  """
101
103
 
@@ -385,7 +387,7 @@ class LinearOperator(ABC):
385
387
 
386
388
  if not is_sparse:
387
389
  if comm is not None:
388
- from feectools.ddm.mpi import mpi as MPI
390
+ from maybempi import MPI
389
391
  comm.Allreduce(MPI.IN_PLACE, out, op=MPI.SUM)
390
392
  return out
391
393
 
@@ -112,9 +112,11 @@ class BlockVectorSpace(VectorSpace):
112
112
 
113
113
  Returns
114
114
  -------
115
- float | complex
115
+ float | complex | cupy.ndarray
116
116
  The scalar product of the two vectors. Note that inner(x, x) is
117
117
  a non-negative real number which is zero if and only if x = 0.
118
+ For vectors with device (CuPy) data, a 0-d device array: the
119
+ result stays on the device.
118
120
 
119
121
  """
120
122
 
@@ -134,7 +136,8 @@ class BlockVectorSpace(VectorSpace):
134
136
  Parameters
135
137
  ----------
136
138
  a : scalar
137
- The scaling coefficient needed for the operation.
139
+ The scaling coefficient needed for the operation (a 0-d device
140
+ array is accepted, see `StencilVectorSpace.axpy`).
138
141
 
139
142
  x : BlockVector
140
143
  The vector which is not modified by this function.
@@ -1076,7 +1079,7 @@ class BlockLinearOperator(LinearOperator):
1076
1079
  if not self.codomain.parallel:
1077
1080
  return blocks, blocks_T
1078
1081
 
1079
- from feectools.ddm.mpi import mpi as MPI
1082
+ from maybempi import MPI
1080
1083
  from feectools.linalg.stencil import StencilInterfaceMatrix
1081
1084
 
1082
1085
  if not isinstance(self.codomain, BlockVectorSpace):
@@ -4,13 +4,14 @@
4
4
  # for full license details. #
5
5
  #---------------------------------------------------------------------------#
6
6
  from abc import abstractmethod
7
+ import numpy as np
7
8
  import cunumpy as xp
8
9
  from cunumpy.xp import array_backend
9
10
  from scipy.sparse import spmatrix, dia_matrix
10
11
 
11
12
  from feectools.linalg.basic import LinearSolver
12
13
 
13
- __all__ = ('to_bnd', 'BandedSolver', 'SparseSolver')
14
+ __all__ = ('to_bnd', 'DenseInverse', 'BandedSolver', 'SparseSolver')
14
15
 
15
16
  #===============================================================================
16
17
  def to_bnd(A):
@@ -28,6 +29,89 @@ def to_bnd(A):
28
29
 
29
30
  return A_bnd, la, ua
30
31
 
32
+ #===============================================================================
33
+ class DenseInverse:
34
+ """
35
+ The inverse of a 1D solver's matrix as a dense matrix, for solves on the device.
36
+
37
+ LAPACK and SuperLU run on the host only. For device data the matrix is inverted
38
+ once on the host, by the solver itself (it solves for the identity), and the inverse
39
+ is applied with one matrix product per solve (a cuBLAS GEMM on CuPy). A device solve
40
+ then makes no host/device copy. The 1D matrices of Kronecker solvers are small
41
+ (n ~ tens to hundreds), so the n x n inverse is cheap to store, and one GEMM over all
42
+ right-hand sides is faster on a GPU than banded triangular solves.
43
+
44
+ Right-hand sides are rows, as in `BandedSolver.solve`: for ``rhs`` of shape
45
+ ``(m, n)`` the solution is ``rhs @ M`` with ``M = solver.solve(I)``, whose row k is
46
+ the solution for the k-th unit vector, i.e. ``M = op(A)^{-T}``. This holds for any
47
+ linear 1D solver with the row convention, transposed or not.
48
+
49
+ Parameters
50
+ ----------
51
+ solver : LinearSolver
52
+ A 1D solver whose ``solve`` accepts host (NumPy) arrays of shape ``(m, n)``.
53
+
54
+ n : int
55
+ The size of the 1D matrix.
56
+
57
+ dtype : dtype
58
+ The dtype of the right-hand sides.
59
+ """
60
+ def __init__(self, solver, n, dtype):
61
+ eye = np.eye(int(n), dtype=dtype)
62
+ # host bookkeeping at setup: the host solver applied to the identity
63
+ self._host = np.ascontiguousarray(xp.to_numpy(solver.solve(eye)))
64
+ self._device = None
65
+
66
+ @property
67
+ def shape(self):
68
+ return self._host.shape
69
+
70
+ @property
71
+ def host_matrix(self):
72
+ """``M`` on the host."""
73
+ return self._host
74
+
75
+ def device_matrix(self):
76
+ """``M`` on the device, copied there once (on the first call)."""
77
+ if self._device is None:
78
+ self._device = xp.to_cupy(self._host)
79
+ return self._device
80
+
81
+ def matrix_for(self, array):
82
+ """``M`` on the device for a device array, on the host otherwise."""
83
+ return self.device_matrix() if xp.is_gpu(array) else self._host
84
+
85
+ def solve(self, rhs, out=None):
86
+ """
87
+ Solves for the right-hand sides ``rhs`` (rows) where they live, with one matrix product.
88
+
89
+ The right-hand sides are the rows of ``rhs``, as in `BandedSolver.solve`, so the
90
+ solutions are the rows of ``rhs @ op(A)^{-T}``; ``M`` stores ``op(A)^{-T}`` (no
91
+ symmetry is assumed). Multiplying from the right keeps the row-contiguous work
92
+ arrays of `KroneckerLinearSolver` as they are: one GEMM, no transpose copy.
93
+
94
+ ``out`` may be ``rhs`` (in-place solve).
95
+ """
96
+ assert rhs.shape[-1] == self._host.shape[0]
97
+ result = rhs @ self.matrix_for(rhs)
98
+ if out is None:
99
+ return result
100
+ assert out.shape == rhs.shape
101
+ out[...] = result
102
+ return out
103
+
104
+ #===============================================================================
105
+ def _device_inverse(solver, n, dtype):
106
+ """The `DenseInverse` of a 1D solver for device solves, built on first use and cached per dtype."""
107
+ cache = getattr(solver, '_dense_inverses', None)
108
+ if cache is None:
109
+ cache = solver._dense_inverses = {}
110
+ key = np.dtype(dtype)
111
+ if key not in cache:
112
+ cache[key] = DenseInverse(solver, n, key)
113
+ return cache[key]
114
+
31
115
  #===============================================================================
32
116
  class BandedSolver(LinearSolver):
33
117
  """
@@ -117,6 +201,7 @@ class BandedSolver(LinearSolver):
117
201
  obj._space = self._space
118
202
  obj._dtype = self._dtype
119
203
  obj._transposed = not self._transposed
204
+ obj._dense_inverses = {}
120
205
 
121
206
  return obj
122
207
 
@@ -140,11 +225,19 @@ class BandedSolver(LinearSolver):
140
225
 
141
226
  transposed = self._transposed
142
227
 
228
+ # LAPACK is host-only: device data is solved with the dense inverse on the
229
+ # device (see DenseInverse). Decided by the array itself, not the global
230
+ # backend, since host arrays may be passed on the CuPy backend too.
231
+ if xp.is_gpu(rhs):
232
+ if out is not None:
233
+ assert out.shape == rhs.shape
234
+ assert out.dtype == rhs.dtype
235
+ return _device_inverse(self, self._bmat.shape[1], rhs.dtype).solve(rhs, out=out)
236
+
143
237
  if out is None:
144
- # LAPACK is host-only: solve on the host, return on the caller's backend.
145
- preout, self._sinfo = self._solver_function(self._bmat, self._l, self._u, xp.to_numpy(rhs).T,
238
+ preout, self._sinfo = self._solver_function(self._bmat, self._l, self._u, rhs.T,
146
239
  self._ipiv, trans=transposed)
147
- out = xp.asarray(preout.T) if xp.is_gpu(rhs) else preout.T
240
+ out = preout.T
148
241
 
149
242
  else:
150
243
  assert out.shape == rhs.shape
@@ -157,16 +250,7 @@ class BandedSolver(LinearSolver):
157
250
  # TODO: handle non-contiguous views?
158
251
 
159
252
  # we want FORTRAN-contiguous data (default is assumed to be C contiguous).
160
- # LAPACK is host-only: a device array is solved in a host copy. Decided by
161
- # the array itself, not the global backend, since host arrays may be passed
162
- # on the CuPy backend too.
163
- if xp.is_gpu(out):
164
- out_cpu = xp.to_numpy(out)
165
- _, self._sinfo = self._solver_function(self._bmat, self._l, self._u, out_cpu.T, self._ipiv, overwrite_b=True,
166
- trans=transposed)
167
- out[...] = xp.asarray(out_cpu)
168
- else:
169
- _, self._sinfo = self._solver_function(self._bmat, self._l, self._u, out.T, self._ipiv, overwrite_b=True,
253
+ _, self._sinfo = self._solver_function(self._bmat, self._l, self._u, out.T, self._ipiv, overwrite_b=True,
170
254
  trans=transposed)
171
255
 
172
256
  return out
@@ -206,6 +290,7 @@ class SparseSolver (LinearSolver):
206
290
  obj._space = self._space
207
291
  obj._splu = self._splu
208
292
  obj._transposed = not self._transposed
293
+ obj._dense_inverses = {}
209
294
 
210
295
  return obj
211
296
 
@@ -229,19 +314,22 @@ class SparseSolver (LinearSolver):
229
314
  assert rhs.T.shape[0] == self._splu.shape[1]
230
315
  transposed = self._transposed
231
316
 
317
+ # SuperLU is host-only: device data is solved with the dense inverse on the
318
+ # device (see DenseInverse); decided by the array, not the global backend.
319
+ if xp.is_gpu(rhs):
320
+ if out is not None:
321
+ assert out.shape == rhs.shape
322
+ assert out.dtype == rhs.dtype
323
+ return _device_inverse(self, self._splu.shape[1], rhs.dtype).solve(rhs, out=out)
324
+
232
325
  if out is None:
233
- # SuperLU is host-only: solve on the host, return on the caller's backend.
234
- out = self._splu.solve(xp.to_numpy(rhs).T, trans='T' if transposed else 'N').T
235
- if xp.is_gpu(rhs):
236
- out = xp.asarray(out)
326
+ out = self._splu.solve(rhs.T, trans='T' if transposed else 'N').T
237
327
 
238
328
  else:
239
329
  assert out.shape == rhs.shape
240
330
  assert out.dtype == rhs.dtype
241
331
 
242
- # currently no in-place solve exposed. SuperLU is host-only; decided by
243
- # the arrays themselves, not the global backend.
244
- result = self._splu.solve(xp.to_numpy(rhs).T, trans='T' if transposed else 'N').T
245
- out[:] = xp.asarray(result) if xp.is_gpu(out) else result
332
+ # currently no in-place solve exposed.
333
+ out[:] = self._splu.solve(rhs.T, trans='T' if transposed else 'N').T
246
334
 
247
335
  return out
@@ -36,6 +36,9 @@ class DistributedFFTBase(LinearOperator):
36
36
  function : Callable
37
37
  The given function.
38
38
  """
39
+ # KroneckerLinearSolver: keep applying the function (not its dense matrix) to device data
40
+ dense_on_device = False
41
+
39
42
  def __init__(self, function):
40
43
  self._function = function
41
44
 
@@ -7,15 +7,16 @@
7
7
  *
8
8
  * One thread per entry of `out` (n_threads = out.size, see __init__.py). A thread outside the owned rows
9
9
  * (local row index i1_loc outside [0, e_out - s_out]) returns without writing, so the padding of `out` is
10
- * left as it is, as in pyccel. Interior rows use 2 * p_in + 1 diagonals, the last owned row (i1 == e_out)
11
- * uses 2 * p_in + add, which is how a rectangular matrix is handled.
10
+ * left as it is, as in pyccel. The matrix has n = mat.shape[1] diagonals (its pads are q = (n - 1) / 2) and
11
+ * diagonal d1 of row i1 is the column i1 - q + d1. Interior rows use all n diagonals, the last owned row
12
+ * (i1 == e_out) uses n - 1 + add, which is how a rectangular matrix is handled.
12
13
  *
13
- * @param mat matrix data, shape (rows of `out`, 2 * p_in + 1)
14
+ * @param mat matrix data, shape (rows of `out`, diagonals)
14
15
  * @param x data of the domain vector, ghost regions included
15
16
  * @param out data of the codomain vector; the owned rows are written
16
17
  * @param s_in global start of the domain of this process
17
18
  * @param p_in padding of the domain
18
- * @param add 1 if the last row uses all 2 * p_in + 1 diagonals, else 0
19
+ * @param add 1 if the last row uses all diagonals, else 0
19
20
  * @param s_out global start of the codomain of this process
20
21
  * @param e_out global end (inclusive) of the codomain of this process
21
22
  * @param p_out padding of the codomain: the owned rows start at index p_out of `mat` and `out`
@@ -30,11 +31,13 @@ extern "C" __global__ void stencil_dot_1d(Array2D<double> mat, Array1D<double> x
30
31
  if (i1_loc < 0 || i1_loc > e_out - s_out) return;
31
32
  const long long i1 = s_out + i1_loc; // global row index
32
33
 
33
- const long long n_diags1 = (i1 == e_out) ? 2 * p_in + add : 2 * p_in + 1;
34
+ const long long n_diags1 = mat.shape[1];
35
+ const long long nd1 = (i1 == e_out) ? n_diags1 - 1 + add : n_diags1;
36
+ const long long off1 = p_in - (n_diags1 - 1) / 2 - s_in; // x index of diagonal 0 minus i1
34
37
 
35
38
  double val = 0.;
36
- for (long long d1 = 0; d1 < n_diags1; ++d1)
37
- val += mat(p_out + i1_loc, d1) * x(i1 + d1 - s_in);
39
+ for (long long d1 = 0; d1 < nd1; ++d1)
40
+ val += mat(p_out + i1_loc, d1) * x(i1 + d1 + off1);
38
41
 
39
42
  out(p_out + i1_loc) = val;
40
43
  }
@@ -0,0 +1,38 @@
1
+ """Stencil matrix-vector product ``out = mat @ x`` on the owned rows (1D).
2
+
3
+ The host version of ``stencil_dot_1d``.
4
+
5
+ Moved from ``feectools.linalg.stencil_dot_kernels.matvec_1d_kernel``. The CUDA version in ``stencil_dot_1d_cuda.cu``
6
+ takes the same arguments in the same order.
7
+
8
+ The number of diagonals is read from ``mat``: ``n = mat.shape[1]`` diagonals, the pads of the matrix are
9
+ ``q = (n - 1) // 2`` (at most the pads ``p_in`` of the domain), and diagonal ``d`` of row ``i`` is the column
10
+ ``i - q + d``. Interior rows use all ``n`` diagonals, the last owned row ``n - 1 + add``.
11
+ """
12
+
13
+
14
+ def stencil_dot_1d(mat: 'float[:, :]',
15
+ x: 'float[:]',
16
+ out: 'float[:]',
17
+ s_in: int,
18
+ p_in: int,
19
+ add: int,
20
+ s_out: int,
21
+ e_out: int,
22
+ p_out: int):
23
+
24
+ n_diags1 = mat.shape[1]
25
+ # x index of diagonal 0 minus the global row index: column i1 - q + d1 is at x[i1 - q + d1 - s_in + p_in]
26
+ off1 = p_in - (n_diags1 - 1) // 2 - s_in
27
+
28
+ for i1 in range(s_out, e_out + 1): # global row index
29
+ i1_loc = i1 - s_out # local row index
30
+ nd1 = n_diags1
31
+ if i1 == e_out:
32
+ nd1 = n_diags1 - 1 + add
33
+
34
+ val = 0.
35
+ for d1 in range(nd1):
36
+ val += mat[p_out + i1_loc, d1] * x[i1 + d1 + off1]
37
+
38
+ out[p_out + i1_loc] = val
@@ -7,11 +7,11 @@
7
7
  *
8
8
  * One thread per entry of `out` (n_threads = out.size, see __init__.py), the last axis varying fastest. A
9
9
  * thread outside the owned rows returns without writing, so the padding of `out` is left as it is, as in
10
- * pyccel. Along each direction k, interior rows use 2 * p_in[k] + 1 diagonals and the last owned row
11
- * (i_k == e_out[k]) uses 2 * p_in[k] + add[k]; the pyccel kernel spells out the four combinations, this
12
- * kernel picks its own per thread. The diagonals are summed in the same order (d1 outer, d2 inner).
10
+ * pyccel. Along each direction k the matrix has n_k = mat.shape[2 + k] diagonals; interior rows use all of
11
+ * them, the last owned row (i_k == e_out[k]) uses n_k - 1 + add[k], as in the 1D kernel. The diagonals are
12
+ * summed in the same order as in pyccel (d1 outer, d2 inner).
13
13
  *
14
- * @param mat matrix data, shape (rows of `out`..., 2 * p_in + 1...)
14
+ * @param mat matrix data, shape (rows of `out`..., diagonals...)
15
15
  * @param x data of the domain vector, ghost regions included
16
16
  * @param out data of the codomain vector; the owned rows are written
17
17
  * @param s_in, p_in, add, s_out, e_out, p_out per direction (length 2), as in the 1D kernel
@@ -30,13 +30,16 @@ extern "C" __global__ void stencil_dot_2d(Array4D<double> mat, Array2D<double> x
30
30
  const long long i1 = s_out[0] + i1_loc; // global row indices
31
31
  const long long i2 = s_out[1] + i2_loc;
32
32
 
33
- const long long n_diags1 = (i1 == e_out[0]) ? 2 * p_in[0] + add[0] : 2 * p_in[0] + 1;
34
- const long long n_diags2 = (i2 == e_out[1]) ? 2 * p_in[1] + add[1] : 2 * p_in[1] + 1;
33
+ const long long nd1 = (i1 == e_out[0]) ? mat.shape[2] - 1 + add[0] : mat.shape[2];
34
+ const long long nd2 = (i2 == e_out[1]) ? mat.shape[3] - 1 + add[1] : mat.shape[3];
35
+ // x index of diagonal 0 minus the global row index, per direction
36
+ const long long off1 = p_in[0] - (mat.shape[2] - 1) / 2 - s_in[0];
37
+ const long long off2 = p_in[1] - (mat.shape[3] - 1) / 2 - s_in[1];
35
38
 
36
39
  double val = 0.;
37
- for (long long d1 = 0; d1 < n_diags1; ++d1)
38
- for (long long d2 = 0; d2 < n_diags2; ++d2)
39
- val += mat(p_out[0] + i1_loc, p_out[1] + i2_loc, d1, d2) * x(i1 + d1 - s_in[0], i2 + d2 - s_in[1]);
40
+ for (long long d1 = 0; d1 < nd1; ++d1)
41
+ for (long long d2 = 0; d2 < nd2; ++d2)
42
+ val += mat(p_out[0] + i1_loc, p_out[1] + i2_loc, d1, d2) * x(i1 + d1 + off1, i2 + d2 + off2);
40
43
 
41
44
  out(p_out[0] + i1_loc, p_out[1] + i2_loc) = val;
42
45
  }
@@ -0,0 +1,47 @@
1
+ """Stencil matrix-vector product ``out = mat @ x`` on the owned rows (2D).
2
+
3
+ The host version of ``stencil_dot_2d``.
4
+
5
+ Moved from ``feectools.linalg.stencil_dot_kernels.matvec_2d_kernel``. The CUDA version in ``stencil_dot_2d_cuda.cu``
6
+ takes the same arguments in the same order.
7
+
8
+ The number of diagonals is read from ``mat``, per direction as in ``stencil_dot_1d``: ``n_k = mat.shape[2 + k]``,
9
+ the pads of the matrix are ``q_k = (n_k - 1) // 2`` and diagonal ``d_k`` of row ``i_k`` is the column
10
+ ``i_k - q_k + d_k``. Interior rows use all ``n_k`` diagonals, the last owned row ``n_k - 1 + add[k]``.
11
+ """
12
+
13
+
14
+ def stencil_dot_2d(mat: 'float[:, :, :, :]',
15
+ x: 'float[:, :]',
16
+ out: 'float[:, :]',
17
+ s_in: 'int[:]',
18
+ p_in: 'int[:]',
19
+ add: 'int[:]',
20
+ s_out: 'int[:]',
21
+ e_out: 'int[:]',
22
+ p_out: 'int[:]'):
23
+
24
+ n_diags1 = mat.shape[2]
25
+ n_diags2 = mat.shape[3]
26
+ # x index of diagonal 0 minus the global row index, per direction
27
+ off1 = p_in[0] - (n_diags1 - 1) // 2 - s_in[0]
28
+ off2 = p_in[1] - (n_diags2 - 1) // 2 - s_in[1]
29
+
30
+ for i1 in range(s_out[0], e_out[0] + 1): # global row indices
31
+ i1_loc = i1 - s_out[0] # local row indices
32
+ nd1 = n_diags1
33
+ if i1 == e_out[0]:
34
+ nd1 = n_diags1 - 1 + add[0]
35
+
36
+ for i2 in range(s_out[1], e_out[1] + 1):
37
+ i2_loc = i2 - s_out[1]
38
+ nd2 = n_diags2
39
+ if i2 == e_out[1]:
40
+ nd2 = n_diags2 - 1 + add[1]
41
+
42
+ val = 0.
43
+ for d1 in range(nd1):
44
+ for d2 in range(nd2):
45
+ val += mat[p_out[0] + i1_loc, p_out[1] + i2_loc, d1, d2] * x[i1 + d1 + off1, i2 + d2 + off2]
46
+
47
+ out[p_out[0] + i1_loc, p_out[1] + i2_loc] = val