nvmath-python 1.0.0__cp314-cp314t-win_amd64.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 (332) hide show
  1. nvmath/__init__.pxd +0 -0
  2. nvmath/__init__.py +45 -0
  3. nvmath/_internal/__init__.py +0 -0
  4. nvmath/_internal/attribute_ifc_factory.py +330 -0
  5. nvmath/_internal/layout.py +70 -0
  6. nvmath/_internal/templates.py +130 -0
  7. nvmath/_internal/threadsafe.py +106 -0
  8. nvmath/_internal/utils.py +43 -0
  9. nvmath/_internal/workspace.py +490 -0
  10. nvmath/_utils.py +147 -0
  11. nvmath/bindings/__init__.py +60 -0
  12. nvmath/bindings/_internal/__init__.pxd +0 -0
  13. nvmath/bindings/_internal/__init__.py +0 -0
  14. nvmath/bindings/_internal/common_types.pxd +31 -0
  15. nvmath/bindings/_internal/cublas.cp314t-win_amd64.pyd +0 -0
  16. nvmath/bindings/_internal/cublas.pxd +530 -0
  17. nvmath/bindings/_internal/cublasLt.cp314t-win_amd64.pyd +0 -0
  18. nvmath/bindings/_internal/cublasLt.pxd +59 -0
  19. nvmath/bindings/_internal/cublasMp.pxd +52 -0
  20. nvmath/bindings/_internal/cudss.cp314t-win_amd64.pyd +0 -0
  21. nvmath/bindings/_internal/cudss.pxd +54 -0
  22. nvmath/bindings/_internal/cufft.cp314t-win_amd64.pyd +0 -0
  23. nvmath/bindings/_internal/cufft.pxd +70 -0
  24. nvmath/bindings/_internal/cufftMp.pxd +77 -0
  25. nvmath/bindings/_internal/curand.cp314t-win_amd64.pyd +0 -0
  26. nvmath/bindings/_internal/curand.pxd +42 -0
  27. nvmath/bindings/_internal/cusolver.cp314t-win_amd64.pyd +0 -0
  28. nvmath/bindings/_internal/cusolver.pxd +15 -0
  29. nvmath/bindings/_internal/cusolverDn.cp314t-win_amd64.pyd +0 -0
  30. nvmath/bindings/_internal/cusolverDn.pxd +406 -0
  31. nvmath/bindings/_internal/cusolverMp.pxd +71 -0
  32. nvmath/bindings/_internal/cusolverSp.cp314t-win_amd64.pyd +0 -0
  33. nvmath/bindings/_internal/cusolverSp.pxd +75 -0
  34. nvmath/bindings/_internal/cusparse.cp314t-win_amd64.pyd +0 -0
  35. nvmath/bindings/_internal/cusparse.pxd +471 -0
  36. nvmath/bindings/_internal/cusparseLt.cp314t-win_amd64.pyd +0 -0
  37. nvmath/bindings/_internal/cusparseLt.pxd +48 -0
  38. nvmath/bindings/_internal/cutensor.cp314t-win_amd64.pyd +0 -0
  39. nvmath/bindings/_internal/cutensor.pxd +58 -0
  40. nvmath/bindings/_internal/mathdx.cp314t-win_amd64.pyd +0 -0
  41. nvmath/bindings/_internal/mathdx.pxd +116 -0
  42. nvmath/bindings/_internal/nvshmem.pxd +29 -0
  43. nvmath/bindings/_internal/utils.cp314t-win_amd64.pyd +0 -0
  44. nvmath/bindings/_internal/utils.pxd +174 -0
  45. nvmath/bindings/_internal/utils.pyi +10 -0
  46. nvmath/bindings/cublas.cp314t-win_amd64.pyd +0 -0
  47. nvmath/bindings/cublas.pxd +558 -0
  48. nvmath/bindings/cublas.pyi +812 -0
  49. nvmath/bindings/cublasLt.cp314t-win_amd64.pyd +0 -0
  50. nvmath/bindings/cublasLt.pxd +109 -0
  51. nvmath/bindings/cublasLt.pyi +1461 -0
  52. nvmath/bindings/cublasMp.pxd +85 -0
  53. nvmath/bindings/cublasMp.pyi +267 -0
  54. nvmath/bindings/cudss.cp314t-win_amd64.pyd +0 -0
  55. nvmath/bindings/cudss.pxd +98 -0
  56. nvmath/bindings/cudss.pyi +443 -0
  57. nvmath/bindings/cufft.cp314t-win_amd64.pyd +0 -0
  58. nvmath/bindings/cufft.pxd +118 -0
  59. nvmath/bindings/cufft.pyi +301 -0
  60. nvmath/bindings/cufftMp.pxd +124 -0
  61. nvmath/bindings/cufftMp.pyi +326 -0
  62. nvmath/bindings/curand.cp314t-win_amd64.pyd +0 -0
  63. nvmath/bindings/curand.pxd +71 -0
  64. nvmath/bindings/curand.pyi +189 -0
  65. nvmath/bindings/cusolver.cp314t-win_amd64.pyd +0 -0
  66. nvmath/bindings/cusolver.pxd +62 -0
  67. nvmath/bindings/cusolver.pyi +320 -0
  68. nvmath/bindings/cusolverDn.cp314t-win_amd64.pyd +0 -0
  69. nvmath/bindings/cusolverDn.pxd +430 -0
  70. nvmath/bindings/cusolverDn.pyi +422 -0
  71. nvmath/bindings/cusolverMp.pxd +98 -0
  72. nvmath/bindings/cusolverMp.pyi +114 -0
  73. nvmath/bindings/cusolverSp.cp314t-win_amd64.pyd +0 -0
  74. nvmath/bindings/cusolverSp.pxd +95 -0
  75. nvmath/bindings/cusolverSp.pyi +70 -0
  76. nvmath/bindings/cusparse.cp314t-win_amd64.pyd +0 -0
  77. nvmath/bindings/cusparse.pxd +546 -0
  78. nvmath/bindings/cusparse.pyi +1017 -0
  79. nvmath/bindings/cusparseLt.cp314t-win_amd64.pyd +0 -0
  80. nvmath/bindings/cusparseLt.pxd +99 -0
  81. nvmath/bindings/cusparseLt.pyi +252 -0
  82. nvmath/bindings/cutensor.cp314t-win_amd64.pyd +0 -0
  83. nvmath/bindings/cutensor.pxd +98 -0
  84. nvmath/bindings/cutensor.pyi +324 -0
  85. nvmath/bindings/cycublas.cp314t-win_amd64.pyd +0 -0
  86. nvmath/bindings/cycublas.pxd +664 -0
  87. nvmath/bindings/cycublasLt.cp314t-win_amd64.pyd +0 -0
  88. nvmath/bindings/cycublasLt.pxd +1045 -0
  89. nvmath/bindings/cycublasMp.pxd +171 -0
  90. nvmath/bindings/cycudss.cp314t-win_amd64.pyd +0 -0
  91. nvmath/bindings/cycudss.pxd +277 -0
  92. nvmath/bindings/cycufft.cp314t-win_amd64.pyd +0 -0
  93. nvmath/bindings/cycufft.pxd +333 -0
  94. nvmath/bindings/cycufftMp.pxd +342 -0
  95. nvmath/bindings/cycurand.cp314t-win_amd64.pyd +0 -0
  96. nvmath/bindings/cycurand.pxd +141 -0
  97. nvmath/bindings/cycusolver.cp314t-win_amd64.pyd +0 -0
  98. nvmath/bindings/cycusolver.pxd +137 -0
  99. nvmath/bindings/cycusolverDn.cp314t-win_amd64.pyd +0 -0
  100. nvmath/bindings/cycusolverDn.pxd +443 -0
  101. nvmath/bindings/cycusolverMp.pxd +107 -0
  102. nvmath/bindings/cycusolverSp.cp314t-win_amd64.pyd +0 -0
  103. nvmath/bindings/cycusolverSp.pxd +93 -0
  104. nvmath/bindings/cycusparse.cp314t-win_amd64.pyd +0 -0
  105. nvmath/bindings/cycusparse.pxd +679 -0
  106. nvmath/bindings/cycusparseLt.cp314t-win_amd64.pyd +0 -0
  107. nvmath/bindings/cycusparseLt.pxd +135 -0
  108. nvmath/bindings/cycutensor.cp314t-win_amd64.pyd +0 -0
  109. nvmath/bindings/cycutensor.pxd +189 -0
  110. nvmath/bindings/cymathdx.cp314t-win_amd64.pyd +0 -0
  111. nvmath/bindings/cymathdx.pxd +552 -0
  112. nvmath/bindings/cynvshmem.pxd +118 -0
  113. nvmath/bindings/mathdx.cp314t-win_amd64.pyd +0 -0
  114. nvmath/bindings/mathdx.pxd +182 -0
  115. nvmath/bindings/mathdx.pyi +1562 -0
  116. nvmath/bindings/nvpl/__init__.pxd +0 -0
  117. nvmath/bindings/nvpl/__init__.py +13 -0
  118. nvmath/bindings/nvpl/_internal/__init__.pxd +0 -0
  119. nvmath/bindings/nvpl/_internal/__init__.py +0 -0
  120. nvmath/bindings/nvpl/_internal/blas.cp314t-win_amd64.pyd +0 -0
  121. nvmath/bindings/nvpl/_internal/blas.pxd +237 -0
  122. nvmath/bindings/nvpl/_internal/fft.cp314t-win_amd64.pyd +0 -0
  123. nvmath/bindings/nvpl/_internal/fft.pxd +36 -0
  124. nvmath/bindings/nvpl/blas.cp314t-win_amd64.pyd +0 -0
  125. nvmath/bindings/nvpl/blas.pxd +131 -0
  126. nvmath/bindings/nvpl/blas.pyi +168 -0
  127. nvmath/bindings/nvpl/cyblas.cp314t-win_amd64.pyd +0 -0
  128. nvmath/bindings/nvpl/cyblas.pxd +280 -0
  129. nvmath/bindings/nvpl/cyfft.cp314t-win_amd64.pyd +0 -0
  130. nvmath/bindings/nvpl/cyfft.pxd +93 -0
  131. nvmath/bindings/nvpl/fft.cp314t-win_amd64.pyd +0 -0
  132. nvmath/bindings/nvpl/fft.pxd +100 -0
  133. nvmath/bindings/nvpl/fft.pyi +168 -0
  134. nvmath/bindings/nvshmem.pxd +54 -0
  135. nvmath/bindings/nvshmem.pyi +191 -0
  136. nvmath/device/__init__.py +38 -0
  137. nvmath/device/_deprecated.py +33 -0
  138. nvmath/device/common.py +315 -0
  139. nvmath/device/common_backend.py +131 -0
  140. nvmath/device/common_cuda.py +201 -0
  141. nvmath/device/common_numba.py +300 -0
  142. nvmath/device/common_numba_cuda_mlir.py +202 -0
  143. nvmath/device/common_opaque_tensor.py +201 -0
  144. nvmath/device/cublasdx.py +1606 -0
  145. nvmath/device/cublasdx_backend.py +860 -0
  146. nvmath/device/cublasdx_numba.py +1534 -0
  147. nvmath/device/cublasdx_numba_cuda_mlir.py +208 -0
  148. nvmath/device/cufftdx.py +373 -0
  149. nvmath/device/cufftdx_backend.py +220 -0
  150. nvmath/device/cufftdx_numba.py +140 -0
  151. nvmath/device/cufftdx_numba_cuda_mlir.py +79 -0
  152. nvmath/device/curand_kernel.py +9147 -0
  153. nvmath/device/cusolverdx.py +2708 -0
  154. nvmath/device/cusolverdx_backend.py +440 -0
  155. nvmath/device/cusolverdx_numba.py +567 -0
  156. nvmath/device/cusolverdx_numba_cuda_mlir.py +604 -0
  157. nvmath/device/cusolverdx_overload_backend.py +1029 -0
  158. nvmath/device/llvm_array.py +29 -0
  159. nvmath/device/random.py +441 -0
  160. nvmath/device/random_helpers.py +23 -0
  161. nvmath/device/random_states.py +187 -0
  162. nvmath/device/types.py +138 -0
  163. nvmath/device/vector_types_numba.py +259 -0
  164. nvmath/distributed/__init__.py +200 -0
  165. nvmath/distributed/_internal/__init__.py +0 -0
  166. nvmath/distributed/_internal/nccl.py +86 -0
  167. nvmath/distributed/_internal/nvshmem.py +307 -0
  168. nvmath/distributed/_internal/symmetric_memory.py +35 -0
  169. nvmath/distributed/_internal/tensor_ifc.py +70 -0
  170. nvmath/distributed/_internal/tensor_ifc_cupy.py +68 -0
  171. nvmath/distributed/_internal/tensor_ifc_host_device.py +172 -0
  172. nvmath/distributed/_internal/tensor_ifc_numpy.py +46 -0
  173. nvmath/distributed/_internal/tensor_ifc_torch.py +162 -0
  174. nvmath/distributed/_internal/tensor_wrapper.py +81 -0
  175. nvmath/distributed/_utils.py +167 -0
  176. nvmath/distributed/distribution/__init__.py +30 -0
  177. nvmath/distributed/distribution/_configuration.py +39 -0
  178. nvmath/distributed/distribution/distributions.py +1024 -0
  179. nvmath/distributed/distribution/redistribute.py +1284 -0
  180. nvmath/distributed/fft/__init__.py +7 -0
  181. nvmath/distributed/fft/_configuration.py +82 -0
  182. nvmath/distributed/fft/fft.py +2742 -0
  183. nvmath/distributed/linalg/__init__.py +22 -0
  184. nvmath/distributed/linalg/_internal/__init__.py +3 -0
  185. nvmath/distributed/linalg/_internal/epilog_protocol.py +586 -0
  186. nvmath/distributed/linalg/_internal/matmul_desc_ifc.py +28 -0
  187. nvmath/distributed/linalg/advanced/__init__.py +8 -0
  188. nvmath/distributed/linalg/advanced/_configuration.py +171 -0
  189. nvmath/distributed/linalg/advanced/matmulmod.py +3573 -0
  190. nvmath/distributed/linalg/generic/__init__.py +8 -0
  191. nvmath/distributed/linalg/generic/_caching.py +66 -0
  192. nvmath/distributed/linalg/generic/_configuration.py +61 -0
  193. nvmath/distributed/linalg/generic/_factorization.py +172 -0
  194. nvmath/distributed/linalg/generic/_initialization.py +966 -0
  195. nvmath/distributed/linalg/generic/_problem_spec.py +511 -0
  196. nvmath/distributed/linalg/generic/solvermod.py +1368 -0
  197. nvmath/distributed/process_group.py +408 -0
  198. nvmath/fft/__init__.py +7 -0
  199. nvmath/fft/_configuration.py +189 -0
  200. nvmath/fft/_exec_utils.py +82 -0
  201. nvmath/fft/_helpers.py +237 -0
  202. nvmath/fft/fft.py +3122 -0
  203. nvmath/internal/__init__.pxd +3 -0
  204. nvmath/internal/__init__.py +10 -0
  205. nvmath/internal/_bindings.cp314t-win_amd64.pyd +0 -0
  206. nvmath/internal/_bindings.pxd +18 -0
  207. nvmath/internal/_device_utils.py +45 -0
  208. nvmath/internal/_layout/__init__.pxd +3 -0
  209. nvmath/internal/_layout/__init__.py +7 -0
  210. nvmath/internal/_layout/_layout.cp314t-win_amd64.pyd +0 -0
  211. nvmath/internal/_layout/_layout.pxd +1303 -0
  212. nvmath/internal/_layout/_layout.pyi +1145 -0
  213. nvmath/internal/enum_utils.py +142 -0
  214. nvmath/internal/formatters.py +87 -0
  215. nvmath/internal/mem_limit.py +51 -0
  216. nvmath/internal/memory.cp314t-win_amd64.pyd +0 -0
  217. nvmath/internal/memory.pxd +13 -0
  218. nvmath/internal/memory.pyi +50 -0
  219. nvmath/internal/ndbuffer/__init__.pxd +3 -0
  220. nvmath/internal/ndbuffer/__init__.py +9 -0
  221. nvmath/internal/ndbuffer/_copy_kernel.cp314t-win_amd64.pyd +0 -0
  222. nvmath/internal/ndbuffer/_copy_kernel.pxd +10 -0
  223. nvmath/internal/ndbuffer/_jit.cp314t-win_amd64.pyd +0 -0
  224. nvmath/internal/ndbuffer/_jit.pxd +7 -0
  225. nvmath/internal/ndbuffer/_ndbuffer.cp314t-win_amd64.pyd +0 -0
  226. nvmath/internal/ndbuffer/_ndbuffer.pxd +40 -0
  227. nvmath/internal/ndbuffer/_ndbuffer.pyi +463 -0
  228. nvmath/internal/ndbuffer/copy_kernel/args.h +34 -0
  229. nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/array_view.h +52 -0
  230. nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/elementwise.h +68 -0
  231. nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/grid_indexer.h +69 -0
  232. nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/transposed.h +242 -0
  233. nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/type_utils.h +39 -0
  234. nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/utils.h +132 -0
  235. nvmath/internal/ndbuffer/copy_kernel/copy_kernel_impl/vec.h +159 -0
  236. nvmath/internal/ndbuffer/copy_kernel/elementwise.h +53 -0
  237. nvmath/internal/ndbuffer/copy_kernel/transposed.h +58 -0
  238. nvmath/internal/package_ifc.py +168 -0
  239. nvmath/internal/package_ifc_cuda.py +57 -0
  240. nvmath/internal/package_ifc_cupy.py +67 -0
  241. nvmath/internal/package_ifc_torch.py +69 -0
  242. nvmath/internal/package_wrapper.py +14 -0
  243. nvmath/internal/tensor_ifc.py +179 -0
  244. nvmath/internal/tensor_ifc_cupy.py +234 -0
  245. nvmath/internal/tensor_ifc_ndbuffer.py +147 -0
  246. nvmath/internal/tensor_ifc_numpy.py +184 -0
  247. nvmath/internal/tensor_ifc_torch.py +178 -0
  248. nvmath/internal/tensor_wrapper.py +160 -0
  249. nvmath/internal/typemaps.py +113 -0
  250. nvmath/internal/utils.py +805 -0
  251. nvmath/linalg/__init__.py +56 -0
  252. nvmath/linalg/_internal/__init__.py +3 -0
  253. nvmath/linalg/_internal/algo_cap_ifc.py +82 -0
  254. nvmath/linalg/_internal/algo_config_ifc.py +43 -0
  255. nvmath/linalg/_internal/batch.py +234 -0
  256. nvmath/linalg/_internal/enum_to_tuples.py +64 -0
  257. nvmath/linalg/_internal/epilog_protocol.py +766 -0
  258. nvmath/linalg/_internal/layout.py +624 -0
  259. nvmath/linalg/_internal/matmul_desc_ifc.py +28 -0
  260. nvmath/linalg/_internal/matmul_pref_ifc.py +27 -0
  261. nvmath/linalg/_internal/matrix_layout_ifc.py +26 -0
  262. nvmath/linalg/_internal/solver_utils.py +432 -0
  263. nvmath/linalg/_internal/typemaps.py +144 -0
  264. nvmath/linalg/_internal/utils.py +157 -0
  265. nvmath/linalg/advanced/__init__.py +8 -0
  266. nvmath/linalg/advanced/_algorithmmod.py +170 -0
  267. nvmath/linalg/advanced/_configuration.py +351 -0
  268. nvmath/linalg/advanced/helpers/__init__.py +5 -0
  269. nvmath/linalg/advanced/helpers/matmul.py +1316 -0
  270. nvmath/linalg/advanced/matmulmod.py +3734 -0
  271. nvmath/linalg/generic/__init__.py +53 -0
  272. nvmath/linalg/generic/_configuration/__init__.py +39 -0
  273. nvmath/linalg/generic/_configuration/layout.py +263 -0
  274. nvmath/linalg/generic/_configuration/match.py +734 -0
  275. nvmath/linalg/generic/_configuration/qualifiers.py +493 -0
  276. nvmath/linalg/generic/_configuration/solver_configuration.py +59 -0
  277. nvmath/linalg/generic/_configuration/wrap.py +217 -0
  278. nvmath/linalg/generic/_dtype.py +15 -0
  279. nvmath/linalg/generic/matmulmod.py +2094 -0
  280. nvmath/linalg/generic/solvermod.py +1301 -0
  281. nvmath/memory.py +279 -0
  282. nvmath/sparse/__init__.py +38 -0
  283. nvmath/sparse/_internal/__init__.py +21 -0
  284. nvmath/sparse/_internal/common_utils.py +147 -0
  285. nvmath/sparse/_internal/cudss_config_ifc.py +702 -0
  286. nvmath/sparse/_internal/cudss_data_ifc.py +399 -0
  287. nvmath/sparse/_internal/cudss_utils.py +506 -0
  288. nvmath/sparse/_internal/cusparse_utils.py +382 -0
  289. nvmath/sparse/_internal/sparse_bsc_ifc.py +303 -0
  290. nvmath/sparse/_internal/sparse_bsr_ifc.py +305 -0
  291. nvmath/sparse/_internal/sparse_coo_ifc.py +256 -0
  292. nvmath/sparse/_internal/sparse_csc_ifc.py +268 -0
  293. nvmath/sparse/_internal/sparse_csr_ifc.py +288 -0
  294. nvmath/sparse/_internal/sparse_dia_ifc.py +242 -0
  295. nvmath/sparse/_internal/sparse_format_helpers.py +601 -0
  296. nvmath/sparse/_internal/sparse_tensor_ifc.py +133 -0
  297. nvmath/sparse/_internal/sparse_ust_ifc.py +141 -0
  298. nvmath/sparse/_internal/utils.py +56 -0
  299. nvmath/sparse/advanced/__init__.py +7 -0
  300. nvmath/sparse/advanced/_configuration.py +227 -0
  301. nvmath/sparse/advanced/direct_solver.py +2069 -0
  302. nvmath/sparse/generic/__init__.py +7 -0
  303. nvmath/sparse/generic/_configuration.py +129 -0
  304. nvmath/sparse/generic/_helpers.py +137 -0
  305. nvmath/sparse/generic/_thunks.py +21 -0
  306. nvmath/sparse/generic/matmulmod.py +2353 -0
  307. nvmath/sparse/ust/__init__.py +7 -0
  308. nvmath/sparse/ust/_converters.py +422 -0
  309. nvmath/sparse/ust/_cpp.py +28 -0
  310. nvmath/sparse/ust/_drawer.py +565 -0
  311. nvmath/sparse/ust/_emitter.py +1033 -0
  312. nvmath/sparse/ust/_jit.py +188 -0
  313. nvmath/sparse/ust/_kernel.py +282 -0
  314. nvmath/sparse/ust/_utils.py +149 -0
  315. nvmath/sparse/ust/interfaces/__init__.py +0 -0
  316. nvmath/sparse/ust/interfaces/torch_interface.py +476 -0
  317. nvmath/sparse/ust/tensor.py +1016 -0
  318. nvmath/sparse/ust/tensor_format.py +957 -0
  319. nvmath/tensor/__init__.py +6 -0
  320. nvmath/tensor/_configuration.py +120 -0
  321. nvmath/tensor/_internal/__init__.py +3 -0
  322. nvmath/tensor/_internal/cutensor_config_ifc.py +279 -0
  323. nvmath/tensor/_internal/cutensor_utils.py +230 -0
  324. nvmath/tensor/_internal/data.py +43 -0
  325. nvmath/tensor/_internal/einsum_parser.py +444 -0
  326. nvmath/tensor/_internal/typemaps.py +96 -0
  327. nvmath/tensor/contract.py +1900 -0
  328. nvmath_python-1.0.0.dist-info/METADATA +134 -0
  329. nvmath_python-1.0.0.dist-info/RECORD +332 -0
  330. nvmath_python-1.0.0.dist-info/WHEEL +5 -0
  331. nvmath_python-1.0.0.dist-info/licenses/LICENSE +177 -0
  332. nvmath_python-1.0.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,511 @@
1
+ # Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ """Per-rank problem spec and cross-rank reducer for ``DirectSolver``."""
6
+
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass, field
10
+ from typing import Any, Literal
11
+
12
+ from nvmath.distributed.distribution import BlockCyclic, ProcessGrid
13
+
14
+ from ._configuration import DirectSolverOptions
15
+
16
+ _SUPPORTED_DTYPES = ("float32", "float64", "complex64", "complex128")
17
+
18
+
19
+ # Operand-index → display label, used in error messages emitted by the
20
+ # leaf checks and the reducer.
21
+ _OPERAND_NAMES = ("A", "b")
22
+
23
+
24
+ def _grid_coords(grid: ProcessGrid, rank: int) -> tuple[int, int]:
25
+ """Return ``(myprow, mypcol)`` of ``rank`` inside a 2-D ``ProcessGrid``.
26
+
27
+ Mirrors :meth:`BlockCyclic._calc_local_shape`:
28
+ - COL_MAJOR: ``(r % nprow, r // nprow)``
29
+ - ROW_MAJOR: ``(r // npcol, r % npcol)``
30
+
31
+ Custom ``process_array`` grids are not handled.
32
+ """
33
+ nprow, npcol = grid.shape
34
+ if grid.layout == ProcessGrid.Layout.COL_MAJOR:
35
+ return rank % nprow, rank // nprow
36
+ return rank // npcol, rank % npcol
37
+
38
+
39
+ @dataclass(slots=True)
40
+ class _DirectSolverProblemSpec:
41
+ @dataclass(slots=True)
42
+ class Options:
43
+ """Serialisable subset of :class:`DirectSolverOptions` for the spec
44
+ reduction. ``logger`` is intentionally excluded.
45
+ The custom ``__init__`` overrides dataclass-generated init so
46
+ we can construct from a :class:`DirectSolverOptions`; ``__eq__`` /
47
+ ``__repr__`` are still auto-generated from the field annotations.
48
+ """
49
+
50
+ def __init__(self, options: DirectSolverOptions):
51
+ self.inplace_a = options.inplace_a
52
+ self.inplace_b = options.inplace_b
53
+ self.blocking = options.blocking
54
+ # Every rank supplies its own handle or none do,
55
+ # so the handle-creation path is taken uniformly.
56
+ self.handle_is_none = options.handle is None
57
+
58
+ inplace_a: bool = True
59
+ inplace_b: bool = True
60
+ blocking: Literal[True, "auto"] = "auto"
61
+ handle_is_none: bool = True
62
+
63
+ # ---- cross-rank-meaningful field ----
64
+
65
+ # local shapes of [A, b] as supplied by the caller
66
+ shapes: list[list[int]]
67
+
68
+ # dtype name of [A, b] as supplied by the caller
69
+ operands_dtypes: list[str]
70
+
71
+ packages: list[str]
72
+ memory_spaces: list[Literal["cuda", "cpu"]]
73
+
74
+ # We type this as ``list[Any]`` because the element type changes mid-life
75
+ # and neither end of the range works as a single static annotation.
76
+ # At construction each element is an arbitrary ``Distribution``; the
77
+ # converter then reassigns the list in place to ``list[BlockCyclic]``
78
+ # (and the finalizer subsequently does ``distributions[1] = promoted``
79
+ # when 1-D b is promoted to 2-D), after which downstream code reads
80
+ # ``BlockCyclic``-only attributes off the elements.
81
+ # ``Distribution`` is too loose for those read sites,
82
+ # ``BlockCyclic`` is too tight for the construction site, and mypy
83
+ # can't track the in-place narrowing, so we use ``Any`` to avoid
84
+ # scattering ``cast(BlockCyclic, ...)`` at every post-converter read.
85
+ # ``list`` (rather than ``Sequence``) so indexed-assignment paths in
86
+ # the finalizer typecheck. The runtime invariant is enforced by an
87
+ # ``isinstance(d, BlockCyclic)`` assert in the converter.
88
+ distributions: list[Any]
89
+
90
+ options: Options
91
+
92
+ nranks: int
93
+
94
+ # is_leaf=True means this _ProblemSpec hasn't yet been merged with another.
95
+ is_leaf: bool = True
96
+
97
+ # ---- leaf-only fields only meaningful while is_leaf=True; the
98
+ # reducer does not preserve any cross-rank meaning for them ----
99
+
100
+ # column-major flags of [A, b]
101
+ is_F: list[bool] = field(default_factory=list)
102
+
103
+ # int device id per operand (or "cpu")
104
+ device_ids: list[int | str] = field(default_factory=list)
105
+
106
+ # distributed runtime's device on this rank
107
+ runtime_device_id: int = -1
108
+
109
+ rank: int = -1
110
+
111
+
112
+ def _leaf_check_operands_ndim(p: _DirectSolverProblemSpec) -> None:
113
+ """
114
+ Validate operand dimensionality: A is 2-D; b is 1-D (single RHS) or
115
+ 2-D (one or more RHS). Per-axis extents are not checked here -- concrete
116
+ extent constraints (e.g. ``A.cols == b.rows``) need the post-allreduce
117
+ global shape and are validated in
118
+ :meth:`_ResolvedProblemSpec.from_merged_spec`.
119
+ """
120
+ assert p.is_leaf, "p.is_leaf must be True"
121
+
122
+ A_shape, b_shape = p.shapes
123
+ if len(A_shape) != 2:
124
+ raise ValueError(f"Operand A on rank {p.rank} must be 2-D (got {len(A_shape)}-D).")
125
+ if len(b_shape) not in (1, 2):
126
+ raise ValueError(f"Operand b on rank {p.rank} must be 1-D or 2-D (got {len(b_shape)}-D).")
127
+
128
+
129
+ def _leaf_check_operands_dtypes_and_layout(p: _DirectSolverProblemSpec) -> None:
130
+ """
131
+ Validate per-operand dtype is supported and the storage is column-major
132
+ (optionally padded along the leading dimension).
133
+ """
134
+ assert p.is_leaf, "p.is_leaf must be True"
135
+
136
+ for i, dtype in enumerate(p.operands_dtypes):
137
+ if dtype not in _SUPPORTED_DTYPES:
138
+ raise ValueError(
139
+ f"Unsupported dtype {dtype} for operand {_OPERAND_NAMES[i]} on rank {p.rank}; "
140
+ f"supported dtypes are {_SUPPORTED_DTYPES}."
141
+ )
142
+
143
+ for i, is_F in enumerate(p.is_F):
144
+ if not is_F:
145
+ raise ValueError(
146
+ f"Operand {_OPERAND_NAMES[i]} on rank {p.rank} must be column-major "
147
+ f"(unit row stride; leading dimension >= local rows)."
148
+ )
149
+
150
+
151
+ def _leaf_check_operands_placement(p: _DirectSolverProblemSpec) -> None:
152
+ """
153
+ Validate package/memory/device consistency and that the
154
+ operands sit on the same device as the distributed runtime on this rank.
155
+ """
156
+ assert p.is_leaf, "p.is_leaf must be True"
157
+
158
+ if len(set(p.packages)) != 1:
159
+ raise ValueError(
160
+ f"The package for {_OPERAND_NAMES[0]} ({p.packages[0]}) and {_OPERAND_NAMES[1]} "
161
+ f"({p.packages[1]}) on process {p.rank} must be the same."
162
+ )
163
+ if len(set(p.memory_spaces)) != 1:
164
+ raise ValueError(
165
+ f"The operands on process {p.rank} are not in the same memory space: got operand memory spaces {p.memory_spaces}"
166
+ )
167
+
168
+ if len(set(p.device_ids)) != 1:
169
+ raise ValueError(
170
+ f"The device id for {_OPERAND_NAMES[0]} ({p.device_ids[0]}) and {_OPERAND_NAMES[1]} "
171
+ f"({p.device_ids[1]}) on process {p.rank} must be the same."
172
+ )
173
+
174
+ # When operands are on GPU, they must be on the same device as the
175
+ # runtime. All operands share one device id (checked just above), so
176
+ # one comparison covers both.
177
+ if p.memory_spaces[0] != "cpu" and p.device_ids[0] != p.runtime_device_id:
178
+ raise RuntimeError(
179
+ f"The operands are not on the same device as the one assigned to the "
180
+ f"distributed runtime on process {p.rank}: operand device ID is "
181
+ f"{p.device_ids[0]} and the runtime device ID is {p.runtime_device_id}"
182
+ )
183
+
184
+
185
+ def _check_block_cyclic_requirements(label: str, dist: BlockCyclic, rank: int) -> None:
186
+ # cusolverMpCreateMatrixDesc itself requires the descriptor's
187
+ # first-process pair (RSRC_A / CSRC_A) to be (0, 0) -- the docs
188
+ # state "Only the value of 0 is currently supported" for both:
189
+ # https://docs.nvidia.com/cuda/cusolvermp/usage/functions.html#cusolvermpcreatematrixdesc
190
+ if dist.first_process != (0, 0):
191
+ raise ValueError(
192
+ f"first_process for {label} on rank {rank} must be (0, 0); got {dist.first_process}. "
193
+ f"This most likely means you set a non-origin first_process when building the "
194
+ f"distribution (the first_process= argument of BlockCyclic / BlockNonCyclic). "
195
+ f"Note a 1-D distribution is promoted to 2-D, so a 1-D first_process like (1,) "
196
+ f"shows up here as (1, 0). Check the first_process you passed."
197
+ )
198
+
199
+ assert dist.ndim == 2, f"distribution for {label} on rank {rank} should be 2-D; got ndim={dist.ndim}"
200
+
201
+ # cusolverMpCreateDeviceGrid only accepts a COL_MAJOR or ROW_MAJOR
202
+ # mapping enum,
203
+ # see https://docs.nvidia.com/cuda/cusolvermp/usage/types.html#cusolvermpgridmapping-t
204
+ if dist.process_grid.layout not in (ProcessGrid.Layout.COL_MAJOR, ProcessGrid.Layout.ROW_MAJOR):
205
+ raise ValueError(
206
+ f"DirectSolver requires a COL_MAJOR or ROW_MAJOR process grid for {label} "
207
+ f"on rank {rank} (got layout {dist.process_grid.layout})."
208
+ )
209
+
210
+
211
+ # ----------------------------------------------------------------------
212
+ # Distribution contract
213
+ # ----------------------------------------------------------------------
214
+ #
215
+ # DirectSolver accepts ``(distribution_for_A, distribution_for_b)``.
216
+ # cuSOLVERMp wants 2-D block-cyclic layout
217
+ # (https://docs.nvidia.com/cuda/cusolvermp/getting_started/index.html#introduction),
218
+ # so whichever user-facing type the caller picked, ``Slab``,
219
+ # ``BlockNonCyclic``, or ``BlockCyclic``, we convert it to ``BlockCyclic``
220
+ # and used the converted distribution objects for cuSOLVERMp.
221
+ # "Convert" here transforms the distribution *object's type*.
222
+ #
223
+ # Once a distribution is 2-D, `_check_block_cyclic_requirements` runs to enforce
224
+ # the cuSOLVERMp-specific requirements: ``first_process == (0, 0)``
225
+ # (RSRC_A / CSRC_A must be 0, see
226
+ # https://docs.nvidia.com/cuda/cusolvermp/usage/functions.html#cusolvermpcreatematrixdesc)
227
+ # and a ``COL_MAJOR`` / ``ROW_MAJOR`` process-grid layout.
228
+ #
229
+ # Legend:
230
+ # shape ndim -- ndim of the local operand array the caller passed.
231
+ # dist ndim -- ndim declared by the distribution (``None`` for ``Slab``,
232
+ # which then adopts the operand's ndim).
233
+ # convert -- re-express the distribution object as a 2-D ``BlockCyclic``
234
+ # (no operand reshape).
235
+ #
236
+ # Operand shape ndim dist ndim distribution handling
237
+ # ------ ---------- --------- -----------------------------------------
238
+ # A 2 2 convert to BlockCyclic
239
+ # A 2 1 convert to BlockCyclic (1-D dist promoted
240
+ # to 2-D; no operand reshape)
241
+ # b 2 2 convert to BlockCyclic
242
+ # b 1 1 convert to BlockCyclic, then promote to 2-D
243
+ # b 1 2 reject in :func:`_leaf_convert_distributions`
244
+ # b 2 1 convert to BlockCyclic (1-D dist promoted
245
+ # to 2-D ``(nprow, 1)``; no operand reshape)
246
+ #
247
+ # The table only lists explicitly-dimensioned distributions. A ``Slab`` has
248
+ # ``dist ndim = None``: it adopts the operand's shape ndim, so it always behaves
249
+ # like the row whose ``dist ndim`` equals ``shape ndim``.
250
+ #
251
+ # A distribution with ``ndim`` outside the supported set ``{1, 2}`` (for both
252
+ # A and b) -- e.g. a ``BlockCyclic`` over a 3-D ``ProcessGrid`` -- is also
253
+ # rejected by the supported-ndim guard in :func:`_leaf_convert_distributions`.
254
+ #
255
+ # The pipeline is split across the leaf/cross-rank stages because the 1-D b
256
+ # promotion can only run *after* :func:`_cross_check_operands_ndim` confirms
257
+ # that every rank agrees b is 1-D (otherwise a 1-D b on one rank and a 2-D b
258
+ # on another would be silently equalized to the same 2-D shape):
259
+ #
260
+ # 1. :func:`_leaf_convert_distributions`
261
+ # 2. :func:`_leaf_check_a_distribution_requirements`
262
+ # 3. cross-rank checks
263
+ # 4. :func:`_leaf_finalize_b_distribution`
264
+
265
+
266
+ def _leaf_convert_distributions(p: _DirectSolverProblemSpec) -> None:
267
+ """Convert each distribution to ``BlockCyclic`` with ndim matching the
268
+ operand's shape.
269
+
270
+ Pure mutation: no BlockCyclic-requirements check. Two pre-checks run
271
+ ahead of ``.to()`` so the user sees ``DirectSolver``-aware messages
272
+ instead of the generic ``"ndim argument (X) doesn't match..."`` from
273
+ :meth:`Distribution._to_checks`:
274
+
275
+ 1. **Supported-ndim guard** (per operand): both A and b accept a 1-D or
276
+ 2-D distribution. ``ndim is None`` (e.g. ``Slab``) is always allowed --
277
+ ``.to()`` picks up our ``ndim=`` argument.
278
+ 2. **1-D b + 2-D dist guard**: a 1-D b paired with a 2-D distribution is
279
+ rejected here (``npcol > 1`` would split the single RHS column across
280
+ process-columns) with a remediation-tailored message. The reverse, a
281
+ 2-D b with a 1-D dist, is allowed: ``.to()`` promotes the 1-D dist to
282
+ its 2-D ``(nprow, 1)`` analog with no operand reshape.
283
+ """
284
+ assert p.is_leaf, "p.is_leaf must be True"
285
+
286
+ # Cardinality check has to happen before any unpacking below so a user
287
+ # who passes a single distribution gets the friendly message instead of
288
+ # "not enough values to unpack".
289
+ if len(p.distributions) != 2:
290
+ msg = f"DirectSolver expects 2 distributions (for A, b) on rank {p.rank}; got {len(p.distributions)}."
291
+ raise ValueError(msg)
292
+
293
+ # Both A and b accept a 1-D or 2-D distribution: a 1-D distribution is a
294
+ # row-/column-only partition that is promoted to its 2-D analog. (b may
295
+ # itself be 1-D, the single-RHS shorthand; A is always 2-D.)
296
+ supported_dist_ndims = (1, 2)
297
+
298
+ new_dists: list[Any] = []
299
+ for i, d in enumerate(p.distributions):
300
+ shape_ndim = len(p.shapes[i])
301
+ if d.ndim is not None:
302
+ if d.ndim not in supported_dist_ndims:
303
+ raise ValueError(
304
+ f"Distribution for {_OPERAND_NAMES[i]} on rank {p.rank} is {d.ndim}-D. "
305
+ f"DirectSolver requires a 1-D or 2-D distribution for {_OPERAND_NAMES[i]}."
306
+ )
307
+
308
+ # The following ``if`` rejects 1-D b with a 2-D distribution.
309
+ # Only b can reach it: :func:`_leaf_check_operands_ndim` runs earlier
310
+ # in the pipeline and rejects any A that isn't 2-D, so A's
311
+ # ``shape_ndim`` is always 2 and never satisfies ``shape_ndim == 1``.
312
+ # But, a 2-D b with a 1-D dist is intentionally allowed:
313
+ # a 1-D dist describes a row-only partition of a (possibly multi-RHS) b,
314
+ # and ``.to(BlockCyclic, ndim=2)`` below promotes it to the equivalent
315
+ # ``(nprow, 1)`` 2-D dist with no operand reshape.
316
+ if shape_ndim == 1 and d.ndim == 2:
317
+ raise ValueError(
318
+ f"Distribution for b on rank {p.rank} is 2-D, but b is 1-D. Either pass b "
319
+ f"as a 2-D (rows, 1) array (and keep the 2-D distribution), or use a 1-D "
320
+ f"distribution (the 1-D-b shorthand for single-RHS solves)."
321
+ )
322
+ new_dists.append(d.to(BlockCyclic, ndim=shape_ndim, copy=True))
323
+ p.distributions = new_dists
324
+ for d in p.distributions:
325
+ assert isinstance(d, BlockCyclic) # only for type checker
326
+
327
+
328
+ def _leaf_check_a_distribution_requirements(p: _DirectSolverProblemSpec) -> None:
329
+ """A's 2-D ``BlockCyclic`` requirements. Runs pre-cross-rank because A is
330
+ always 2-D after :func:`_leaf_convert_distributions` -- there's nothing
331
+ for it to wait on. b's requirements are checked post-cross-rank in
332
+ :func:`_leaf_finalize_b_distribution` because they may need the 1-D -> 2-D
333
+ promotion to have run first.
334
+ """
335
+ assert p.is_leaf, "p.is_leaf must be True"
336
+ _check_block_cyclic_requirements("A", p.distributions[0], p.rank)
337
+
338
+
339
+ def _cross_check_operands_ndim(p1: _DirectSolverProblemSpec, p2: _DirectSolverProblemSpec) -> None:
340
+ """
341
+ Raise if any operand's number of dimensions disagrees across ranks.
342
+ Must run before :func:`_leaf_finalize_b_distribution` so a 1-D b
343
+ on one rank and a 2-D b on another surfaces as an explicit ndim
344
+ mismatch instead of being silently normalized to the same 2-D shape.
345
+ """
346
+ for i in range(2):
347
+ if len(p1.shapes[i]) != len(p2.shapes[i]):
348
+ raise ValueError(
349
+ f"The number of dimensions of the operand {_OPERAND_NAMES[i]} is inconsistent "
350
+ f"across processes: {len(p1.shapes[i])} != {len(p2.shapes[i])}"
351
+ )
352
+
353
+
354
+ def _cross_check_operands_dtypes(p1: _DirectSolverProblemSpec, p2: _DirectSolverProblemSpec) -> None:
355
+ for i in range(2):
356
+ if p1.operands_dtypes[i] != p2.operands_dtypes[i]:
357
+ raise ValueError(
358
+ f"Operand {_OPERAND_NAMES[i]} dtype does not match across processes: "
359
+ f"{p1.operands_dtypes[i]} != {p2.operands_dtypes[i]}"
360
+ )
361
+
362
+
363
+ def _cross_check_operands_placement(p1: _DirectSolverProblemSpec, p2: _DirectSolverProblemSpec) -> None:
364
+ if p1.packages[0] != p2.packages[0]:
365
+ raise ValueError(f"Operands do not belong to the same package on all processes: {p1.packages[0]} != {p2.packages[0]}")
366
+ if p1.memory_spaces[0] != p2.memory_spaces[0]:
367
+ raise ValueError(
368
+ f'Operands are not in the same memory space ("cpu", "cuda") on all processes: '
369
+ f"{p1.memory_spaces[0]} != {p2.memory_spaces[0]}"
370
+ )
371
+
372
+
373
+ def _cross_check_options(p1: _DirectSolverProblemSpec, p2: _DirectSolverProblemSpec) -> None:
374
+ for attr in ("inplace_a", "inplace_b", "blocking"):
375
+ v1 = getattr(p1.options, attr)
376
+ v2 = getattr(p2.options, attr)
377
+ if v1 != v2:
378
+ raise ValueError(f"DirectSolverOptions.{attr} is inconsistent across processes: {v1!r} != {v2!r}")
379
+
380
+ # A handle's pointer value is per-rank, so only its presence is compared:
381
+ # either every rank supplies its own handle or none do.
382
+ if p1.options.handle_is_none != p2.options.handle_is_none:
383
+ raise ValueError(
384
+ "DirectSolverOptions.handle presence is inconsistent across processes: "
385
+ "some ranks supplied a handle while others did not; either pass a handle "
386
+ "on every rank or on none."
387
+ )
388
+
389
+
390
+ def _cross_check_distributions(p1: _DirectSolverProblemSpec, p2: _DirectSolverProblemSpec) -> None:
391
+ for i, (d1, d2) in enumerate(zip(p1.distributions, p2.distributions, strict=False)):
392
+ if d1 != d2:
393
+ raise ValueError(f"Distribution for {_OPERAND_NAMES[i]} doesn't match across processes: {d1} != {d2}")
394
+
395
+
396
+ def _leaf_finalize_b_distribution(p: _DirectSolverProblemSpec) -> None:
397
+ """Post-cross-rank b-distribution handling.
398
+
399
+ Two responsibilities:
400
+
401
+ 1. **Promote 1-D b to 2-D if needed.** Users may pass b as a 1-D array of
402
+ length ``rows`` (paired with a 1-D distribution) as shorthand for a
403
+ single-RHS solve (NRHS=1). We convert the 1-D distribution to
404
+ the 2-D analog of the distribution.
405
+
406
+ 2. **Check b's 2-D ``BlockCyclic`` requirements.** Runs unconditionally on
407
+ the (now always 2-D) ``p.distributions[1]``.
408
+
409
+ Why post-cross-rank: step 1 must wait for :func:`_cross_check_operands_ndim`
410
+ to confirm every rank agrees b is 1-D, otherwise a 1-D b on one rank and
411
+ a 2-D b on another would be silently normalized to the same 2-D shape.
412
+
413
+ Precondition: :func:`_leaf_convert_distributions` has run, so
414
+ ``p.distributions[1]`` is a ``BlockCyclic`` whose ndim matches
415
+ ``len(p.shapes[1])``.
416
+ """
417
+ assert p.is_leaf, "p.is_leaf must be True"
418
+
419
+ b_shape = p.shapes[1]
420
+ if len(b_shape) == 1:
421
+ dist_b = p.distributions[1]
422
+ assert dist_b.ndim == 1, "1-D b shape paired with non-1-D dist should have been rejected by the converter"
423
+ assert not dist_b._bound, "user-supplied distributions should be not bound"
424
+ # Promote the 1-D (nprow,) distribution to its 2-D (nprow, 1) analog.
425
+ # The promoted grid's layout is irrelevant since one axis is size 1.
426
+ p.distributions[1] = dist_b.to(BlockCyclic, ndim=2)
427
+ # The promoted dist has process grid (nprow, 1), so every rank owns
428
+ # the single global RHS column → local b is (rows, 1).
429
+ p.shapes[1] = [b_shape[0], 1]
430
+
431
+ _check_block_cyclic_requirements("b", p.distributions[1], p.rank)
432
+
433
+
434
+ def _leaf_mask_local_shapes(p: _DirectSolverProblemSpec) -> None:
435
+ """
436
+ Mirrors ``matmulmod._problem_spec_reducer``.
437
+ """
438
+ assert p.is_leaf, "p.is_leaf must be True"
439
+
440
+ for i in range(2):
441
+ d = p.distributions[i]
442
+ if d._is_1d_distribution():
443
+ continue
444
+ myprow, mypcol = _grid_coords(d.process_grid, p.rank)
445
+ if myprow != 0:
446
+ p.shapes[i][1] = 0
447
+ if mypcol != 0:
448
+ p.shapes[i][0] = 0
449
+
450
+
451
+ def _accumulate_partitioned_dims(p1: _DirectSolverProblemSpec, p2: _DirectSolverProblemSpec) -> None:
452
+ """
453
+ Mirrors ``matmulmod._problem_spec_reducer``.
454
+ """
455
+ for i in range(2):
456
+ d = p1.distributions[i]
457
+ partitioned_dims = (0,) if d._is_row_wise() else (1,) if d._is_col_wise() else (0, 1)
458
+ if len(partitioned_dims) == 1 and any(p1.shapes[i][j] != p2.shapes[i][j] for j in (0, 1) if j != partitioned_dims[0]):
459
+ raise ValueError(
460
+ f"The {_OPERAND_NAMES[i]} non-partitioned shape is inconsistent across "
461
+ f"processes: {p1.shapes[i]} != {p2.shapes[i]}"
462
+ )
463
+ if p1 is not p2: # nranks==1 fast path passes p1 is p2
464
+ for dim in partitioned_dims:
465
+ p1.shapes[i][dim] += p2.shapes[i][dim]
466
+
467
+
468
+ def _direct_solver_problem_spec_reducer(p1, p2) -> _DirectSolverProblemSpec | Exception:
469
+ """
470
+ Pairwise reduction op for ``allreduce_object`` over the problem spec.
471
+
472
+ Running inside this reducer means every check above happens during the
473
+ ``allreduce_object`` collective: a failure on any rank becomes an
474
+ ``Exception``, so every rank raises the same error.
475
+ """
476
+ # IMPORTANT: the order below matters since some checks depend on others
477
+ # do not change it before understanding the dependencies.
478
+
479
+ try:
480
+ # Early exit on an ``Exception`` from an earlier merge or local build.
481
+ if isinstance(p1, Exception):
482
+ return p1
483
+ if isinstance(p2, Exception):
484
+ return p2
485
+
486
+ for p in (p1, p2):
487
+ if p.is_leaf:
488
+ _leaf_check_operands_ndim(p)
489
+ _leaf_check_operands_dtypes_and_layout(p)
490
+ _leaf_check_operands_placement(p)
491
+ _leaf_convert_distributions(p)
492
+ _leaf_check_a_distribution_requirements(p)
493
+
494
+ _cross_check_operands_ndim(p1, p2)
495
+ _cross_check_operands_dtypes(p1, p2)
496
+ _cross_check_operands_placement(p1, p2)
497
+ _cross_check_options(p1, p2)
498
+ _cross_check_distributions(p1, p2)
499
+
500
+ for p in (p1, p2):
501
+ if p.is_leaf:
502
+ _leaf_finalize_b_distribution(p)
503
+ _leaf_mask_local_shapes(p)
504
+
505
+ _accumulate_partitioned_dims(p1, p2)
506
+
507
+ except Exception as e:
508
+ return e
509
+
510
+ p1.is_leaf = False
511
+ return p1