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,1016 @@
1
+ # Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. ALL RIGHTS RESERVED.
2
+ #
3
+ # SPDX-License-Identifier: Apache-2.0
4
+
5
+ """
6
+ This module defines the universal sparse tensor (UST).
7
+ """
8
+
9
+ __all__ = ["Tensor"]
10
+
11
+ import contextlib
12
+ import re
13
+ from typing import Literal
14
+
15
+ import numpy as np
16
+ from cuda import pathfinder
17
+
18
+ from nvmath._internal.layout import is_contiguous_and_dense
19
+ from nvmath.internal import tensor_wrapper, utils
20
+ from nvmath.sparse._internal.common_utils import sparse_or_dense, wrap_sparse_operand
21
+
22
+ from ._converters import TensorConverter
23
+ from ._drawer import animate_tensor, draw_tensor, draw_tensor_raw, draw_tensor_storage
24
+ from ._emitter import emit_apply_kernel, populate_apply_parameters
25
+ from ._jit import compile_cpp_and_link, compile_python_function, launch_kernel
26
+ from ._utils import LevelMap, resolve_stream, stream_context
27
+ from .tensor_format import Dimension, LevelFormat, TensorFormat, is_unique
28
+
29
+
30
+ def _tensor_to_list(wrapped_operand, truncate=32):
31
+ m = wrapped_operand.memory_buffer()
32
+ stream = None if m.device_id == "cpu" else utils.get_or_create_stream(m.device_id, None, m.name)
33
+ o = [str(v.item()) for v in m.__class__(m.tensor[:truncate]).to("cpu", stream).tensor]
34
+ if m.size > truncate:
35
+ o += ["..."]
36
+ if m.size > 2 * truncate:
37
+ o += [str(v.item()) for v in m.__class__(m.tensor[-truncate:]).to("cpu", stream).tensor]
38
+
39
+ return "[{}]".format(", ".join(o))
40
+
41
+
42
+ def _top_array(value, *, dtype, device_id, dense_tensorholder_type, stream_holder):
43
+ a = utils.create_empty_tensor(dense_tensorholder_type, (2,), dtype, device_id, stream_holder, verify_strides=False)
44
+ a.tensor[0], a.tensor[1] = 0, value
45
+ return a
46
+
47
+
48
+ def _axis_order_in_memory(shape, strides):
49
+ """
50
+ Compute the order in which the axes appear in memory.
51
+ """
52
+ # Scalars.
53
+ if len(shape) == 0:
54
+ return []
55
+
56
+ # The shape is used to resolve cases like (1, 2, 1) : (2, 1, 1) in CuTe notation.
57
+ # Use numpy's lexsort for better performance (avoids creating intermediate tuples)
58
+ ndim = len(strides)
59
+ axis_order = np.lexsort((np.arange(ndim), shape, strides))
60
+ return tuple(axis_order)
61
+
62
+
63
+ def _calculate_strides(shape, axis_order):
64
+ """
65
+ Calculate the strides for the provided shape and axis order.
66
+ """
67
+ strides = [None] * len(shape)
68
+
69
+ stride = 1
70
+ for axis in axis_order:
71
+ strides[axis] = stride
72
+ stride *= shape[axis]
73
+
74
+ return strides
75
+
76
+
77
+ def _dense_ust_axis_order(tensor_format, shape):
78
+ """
79
+ Compute the strides for a dense contiguous N-D tensor represented as a UST.
80
+ """
81
+ dimensions = tensor_format.dimensions
82
+ level_keys = list(tensor_format.levels.keys())
83
+ axis_order = [None] * len(dimensions)
84
+ for d, label in enumerate(dimensions):
85
+ index = level_keys.index(label)
86
+ axis_order[index] = d
87
+
88
+ return list(reversed(axis_order))
89
+
90
+
91
+ def _get_smallest_index_type(index):
92
+ dtypes = [np.int32, np.int64]
93
+ for dtype in dtypes:
94
+ try:
95
+ dtype(index)
96
+ break
97
+ except OverflowError:
98
+ pass
99
+
100
+ return np.dtype(dtype).name
101
+
102
+
103
+ SHARED_UST_DOCUMENTATION = utils.COMMON_SHARED_DOC_MAP.copy()
104
+ SHARED_UST_DOCUMENTATION.update(
105
+ {
106
+ #
107
+ "extents": "The extents (shape) of the sparse tensor as a Python sequence",
108
+ #
109
+ "tensor_format": "The sparse tensor format as a :class:`TensorFormat` object.",
110
+ #
111
+ "index_type": """\
112
+ The index type of the tensor as a NumPy-recognizable string or CudaDataType.""".replace("\n", " "),
113
+ #
114
+ "dtype": """\
115
+ The datatype of the tensor as a NumPy-recognizable string or CudaDataType.""".replace("\n", " "),
116
+ }
117
+ )
118
+
119
+
120
+ @utils.docstring_decorator(SHARED_UST_DOCUMENTATION, skip_missing=False)
121
+ class Tensor:
122
+ """
123
+ The universal sparse tensor binds extents, data type, index type, etc with a universal
124
+ sparse tensor format object.
125
+
126
+ .. note:: The constructor is currently private and for internal use only. Users should
127
+ use any of the methods :meth:`Tensor.from_package`, :meth:`Tensor.from_file`, or
128
+ :meth:`Tensor.convert` to construct a UST.
129
+
130
+
131
+ Args:
132
+ extents: {extents}
133
+ tensor_format: {tensor_format}
134
+ index_type: {index_type}
135
+ dtype: {dtype}
136
+ package: The package using which the dense tensors underlying the sparse
137
+ representation will be allocated. It must be a Python module object.
138
+ """
139
+
140
+ def __init__(
141
+ self, extents, *, tensor_format=None, index_type="int32", dtype="float32", package=None, options=None, stream=None
142
+ ):
143
+ if tensor_format is None:
144
+ raise TypeError("The tensor format must be specified.")
145
+ if not isinstance(tensor_format, TensorFormat):
146
+ raise TypeError(f"The tensor format {tensor_format} must be a TensorFormat object.")
147
+ if len(extents) != tensor_format.num_dimensions:
148
+ raise TypeError(
149
+ f"The tensor rank {len(extents)} must equal the format dimensionality \
150
+ {tensor_format.num_dimensions}."
151
+ )
152
+ if np.prod(extents) <= 0:
153
+ raise TypeError(f"The tensor extents {extents} do not define any elements.")
154
+
155
+ assert options is None, "Internal error."
156
+
157
+ self._extents = list(extents)
158
+ self._levels = tensor_format.dim2lvl(extents, True)
159
+ self._tensor_format = tensor_format
160
+ self._index_type = index_type
161
+ self._dtype = dtype
162
+ self._package = package
163
+
164
+ # The UST uses a dictionary from "levels" to the package arrays for the positions
165
+ # and coordinates, and a direct array for values.
166
+ self._pos = LevelMap()
167
+ self._crd = LevelMap()
168
+ self._val = None
169
+
170
+ # Set wrapped operand.
171
+ self._wrapped_operand = None
172
+
173
+ # Kernel.
174
+ self._kernel = None
175
+ self._with_indices = False
176
+
177
+ @classmethod
178
+ def from_file(cls, filename, stream=None):
179
+ """
180
+ A helper to create a universal sparse tensor (in COO representation) from a file.
181
+ The supported file formats are the Matrix Market format (.mtx) for matrices and
182
+ the FROSTT format (.tns) for tensors.
183
+
184
+ Args:
185
+ filename: The path to the source file (with a '.mtx' or '.tns' extension).
186
+ stream: {stream}
187
+ """
188
+ message = f"The format of {filename} is not recognized. The supported extensions are '.mtx' and '.tns'"
189
+ m = re.match(r".*\.(\w+)", filename)
190
+ if m is None:
191
+ raise TypeError(message)
192
+ suffix = m.group(1)
193
+
194
+ if suffix == "mtx":
195
+ # As of version 1.12, scipy.io.mmread is based on fast_matrix_market. This
196
+ # provides the fastest way to read from the Matrix Market and simplifies
197
+ # getting the COO in canonical format.
198
+
199
+ try:
200
+ import scipy as sp
201
+ except ImportError:
202
+ raise ImportError("SciPy is required for this functionality.") from None
203
+
204
+ coo = sp.io.mmread(filename, spmatrix=True)
205
+ coo.sum_duplicates()
206
+ return cls.from_package(coo, stream=stream)
207
+
208
+ if suffix == "tns":
209
+ # Convenience Python utility to map file data to indices and values. Note
210
+ # that we could use this map to construct the UST directly as an unordered
211
+ # and nonunique COO format. However, we go through torch tensor to simplify
212
+ # getting the COO in canonical format.
213
+ try:
214
+ import torch
215
+ except ImportError:
216
+ raise ImportError("PyTorch is required for this functionality.") from None
217
+
218
+ data = np.genfromtxt(filename, comments="#", unpack=True, dtype=None)
219
+ num_dimensions = len(data) - 1
220
+
221
+ if num_dimensions < 1:
222
+ raise ValueError(f"cannot parse {filename} with FROSTT structure")
223
+
224
+ extents = tuple(max(data[d]) for d in range(num_dimensions))
225
+ indices = torch.stack([torch.as_tensor(data[d] - 1) for d in range(num_dimensions)], dim=0)
226
+ values = data[num_dimensions]
227
+
228
+ coo = torch.sparse_coo_tensor(indices, values, extents)
229
+ return cls.from_package(coo.coalesce(), stream=stream)
230
+
231
+ raise TypeError(message)
232
+
233
+ @classmethod
234
+ def from_package(cls, tensor, stream=None):
235
+ """
236
+ Create an universal sparse tensor from a sparse package tensor. This is a
237
+ zero-copy operation where the UST shares a view of the sparse data with the
238
+ sparse tensor. A strided tensor (like :class:`numpy.ndarray` or
239
+ :class:`torch.Tensor`) can also be viewed as a UST, with the current limitation
240
+ that there are no "holes" (the strided tensor is dense, in other words).
241
+
242
+ The currently supported libraries are NumPy, SciPy, CuPy, and PyTorch and
243
+ both CPU and GPU (CUDA) memory spaces are supported.
244
+
245
+ Args:
246
+ tensor: The source sparse tensor from NumPy, SciPy, CuPy, or PyTorch packages.
247
+ stream: {stream}
248
+
249
+ Returns:
250
+ A UST view of the sparse or dense package tensor.
251
+ """
252
+ # Note: since the API takes in a stream object, it's the user's responsibility
253
+ # to ensure ordering of operations wrt to the provided stream. If we decide to use
254
+ # a package stream for UST, then we will internally order it wrt the user stream.
255
+ # The `wrapped.operand.to_ust()` method does this if a stream pointer is provided.
256
+
257
+ tensor_type = sparse_or_dense(tensor)
258
+ if tensor_type == "sparse":
259
+ try:
260
+ wrapped_operand = wrap_sparse_operand(tensor)
261
+ # If we decide to use a package stream for UST, then we will internally
262
+ # order it wrt the user stream. The `wrapped.operand.to_ust()` method does
263
+ # this if a stream pointer is provided.
264
+ device_id = wrapped_operand.device_id
265
+ device_ctx = contextlib.nullcontext() if device_id == "cpu" else utils.device_ctx(device_id)
266
+ with device_ctx:
267
+ ust = wrapped_operand.to_ust(stream=stream)
268
+ ust._wrapped_operand = wrapped_operand
269
+ return ust
270
+ except Exception as e:
271
+ raise TypeError(f"The sparse operand type {type(tensor)} is unsupported or invalid.") from e
272
+
273
+ # If we decide to use a package stream for UST, then we will internally order
274
+ # it wrt the user stream. The `synchronize_dense_tensor` function does this
275
+ # for dense operands.
276
+
277
+ assert tensor_type == "dense", "Internal error."
278
+
279
+ return cls._from_dense(tensor)
280
+
281
+ def _is_dense_format(self):
282
+ """
283
+ Check if the UST representation is a dense strided layout.
284
+ """
285
+ # TODO: Use the level spec instead of inferring from the name.
286
+ return self.tensor_format.name.startswith("Scalar") or self.tensor_format.name.startswith("Dense")
287
+
288
+ def _dense_tensor_from_buffer(self):
289
+ """
290
+ Create a view of the buffer as a dense tensor (only applicable to strided layout).
291
+ """
292
+ shape = self.extents
293
+ axis_order = _dense_ust_axis_order(self.tensor_format, shape)
294
+ strides = _calculate_strides(shape, axis_order)
295
+
296
+ wrapped_dense = self._val.memory_buffer_to_tensor(shape, strides) # unflatten
297
+
298
+ return wrapped_dense
299
+
300
+ def to_package(self, options=None, stream=None):
301
+ """
302
+ Create a sparse package tensor from the universal sparse tensor. This is a
303
+ zero-copy operation where the UST shares a view of the sparse data with the package
304
+ tensor.
305
+
306
+ Returns:
307
+ The sparse or dense package tensor view into this UST.
308
+ """
309
+ device_id = self.device_id
310
+ device_ctx = contextlib.nullcontext() if device_id == "cpu" else utils.device_ctx(device_id)
311
+ with device_ctx:
312
+ if self._is_dense_format():
313
+ # Extract from the wrapped operand, if it exists.
314
+ if self._wrapped_operand is not None:
315
+ return self._wrapped_operand.tensor
316
+
317
+ # Otherwise, create a new dense tensor view.
318
+ return self._dense_tensor_from_buffer().tensor
319
+
320
+ return self._wrapped_operand.to_package()
321
+
322
+ def convert(self, *, tensor_format=None, index_type=None, dtype=None, copy=None, stream=None):
323
+ """
324
+ Convert a UST into a new UST with the specified format, index type, and data
325
+ type. The default values of these for the target UST are taken from the
326
+ corresponding ones in the source UST.
327
+
328
+ Please note that this is a proof-of-concept implementation that supports
329
+ converting any UST format into any other UST format. Even though some
330
+ fast-path solutions are provided, the current general solution in
331
+ :class:`TensorConverter` has not been optimized for speed yet.
332
+
333
+ .. note:: The target shares a source kernel only for trivial conversions.
334
+
335
+ Args:
336
+ tensor_format: {tensor_format}
337
+ index_type: {index_type}
338
+ dtype: {dtype}
339
+ copy: for the case where the target tensor format, data and index
340
+ types match those of the source object, return the same object if
341
+ ``copy=None`` or return a copy if ``copy=True``. In the complementary
342
+ case (where any of tensor format, data or index types don't match)
343
+ a copy is always made.
344
+
345
+ Returns:
346
+ A UST in the specified format, with the specified index and data types.
347
+ """
348
+ if tensor_format is None:
349
+ tensor_format = self.tensor_format
350
+ if index_type is None:
351
+ index_type = self.index_type
352
+ if dtype is None:
353
+ dtype = self.dtype
354
+
355
+ # Fast path.
356
+ if self.tensor_format.name == tensor_format.name and self.index_type == index_type and self.dtype == dtype:
357
+ return self.clone(stream=stream) if copy else self
358
+
359
+ target = Tensor(
360
+ self.extents,
361
+ tensor_format=tensor_format,
362
+ index_type=index_type,
363
+ dtype=dtype,
364
+ stream=stream,
365
+ )
366
+ TensorConverter(self, target).run(stream=stream)
367
+
368
+ # Set wrapped operand.
369
+ if target._is_dense_format():
370
+ target._wrapped_operand = target._dense_tensor_from_buffer()
371
+ else:
372
+ target._wrapped_operand = wrap_sparse_operand(target)
373
+
374
+ return target
375
+
376
+ def to(self, device_id: int | Literal["cpu"], stream=None):
377
+ """Copy the UST to a different device (contents only).
378
+
379
+ The sparse representation is not copied (it is a view) if the UST
380
+ is already on the requested device.
381
+
382
+ .. note:: Any kernel associated with the source is not copied over to target.
383
+
384
+ Args:
385
+ device_id: The CUDA device ordinal, or "cpu".
386
+ stream: {stream}
387
+
388
+ Returns:
389
+ A copy of the UST on the specified device.
390
+ """
391
+ package = self._dense_tensorholder_type.name
392
+
393
+ if (device_id == "cpu") != (self.device_id == "cpu") and package != "torch":
394
+ raise NotImplementedError(
395
+ f"The to() operation from memory space {self.device_id} to memory space {device_id} \
396
+ currently requires the UST to be backed by PyTorch storage."
397
+ )
398
+
399
+ if (
400
+ isinstance(device_id, int)
401
+ and isinstance(self.device_id, int)
402
+ and device_id != self.device_id
403
+ and package != "torch"
404
+ ):
405
+ raise ValueError(
406
+ "The to() operation currently supports copying across different devices only for \
407
+ a UST backed by PyTorch storage."
408
+ )
409
+
410
+ stream_holder = resolve_stream(stream, self, device_id)
411
+ return self._to(device_id, stream_holder)
412
+
413
+ def _to(self, device_id: int | Literal["cpu"], stream_holder):
414
+ target = Tensor(
415
+ self.extents,
416
+ tensor_format=self.tensor_format,
417
+ index_type=self.index_type,
418
+ dtype=self.dtype,
419
+ package=self._package,
420
+ options=None,
421
+ stream=stream_holder,
422
+ )
423
+
424
+ # Set the UST data.
425
+ target._pos = self._pos.to(device_id, stream_holder)
426
+ target._crd = self._crd.to(device_id, stream_holder)
427
+ assert self._val is not None, "Internal error: UST is only partially constructed."
428
+ target._val = self._val.to(device_id, stream_holder)
429
+
430
+ # Set wrapped operand.
431
+ if target._is_dense_format():
432
+ target._wrapped_operand = target._dense_tensor_from_buffer()
433
+ else:
434
+ target._wrapped_operand = wrap_sparse_operand(target)
435
+
436
+ return target
437
+
438
+ def copy_(self, src, stream=None):
439
+ """
440
+ Copy a compatible UST into this one, where compatible means that the source
441
+ UST has the same shape, tensor format, non-zero structure, index type, and data
442
+ type as this one.
443
+
444
+ .. note:: Any kernel associated with the source is not copied over to target, and
445
+ any kernel that was associated with this one is reset to None.
446
+
447
+ Args:
448
+ src: The source UST (:class:`nvmath.sparse.ust.Tensor`) that will be
449
+ copied into this one.
450
+ stream: {stream}
451
+ """
452
+
453
+ if self.tensor_format.name != src.tensor_format.name:
454
+ raise ValueError("The source and target formats are not compatible.")
455
+
456
+ if self.extents != src.extents:
457
+ raise ValueError(f"The source {src.extents} and target {self.extents} extents are not compatible.")
458
+
459
+ if self.index_type != src.index_type:
460
+ raise ValueError(f"The source {src.index_type} and target {self.index_type} index types are not compatible.")
461
+
462
+ if self.dtype != src.dtype:
463
+ raise ValueError(f"The source {src.dtype} and target {self.dtype} dtypes are not compatible.")
464
+
465
+ stream_holder = resolve_stream(stream, src, self.device_id)
466
+
467
+ # Copy the UST data.
468
+ self._pos.copy_(src._pos, stream_holder)
469
+ self._crd.copy_(src._crd, stream_holder)
470
+ assert self._val is not None, "Internal error: UST is only partially constructed."
471
+ self._val.copy_(src._val, stream_holder)
472
+
473
+ self._kernel = None
474
+ self._with_indices = False
475
+
476
+ def clone(self, stream=None):
477
+ """
478
+ Clone the UST. The sparse representation of the UST is copied to the target, while
479
+ any associated kernel is shared between source and target.
480
+
481
+ .. note:: Any kernel associated with the source is shared with the target.
482
+
483
+ Args:
484
+ stream: {stream}
485
+
486
+ Returns:
487
+ A clone of this UST.
488
+ """
489
+ stream_holder = resolve_stream(stream, self)
490
+
491
+ target = Tensor(
492
+ self.extents,
493
+ tensor_format=self.tensor_format,
494
+ index_type=self.index_type,
495
+ dtype=self.dtype,
496
+ package=self._package,
497
+ options=None,
498
+ stream=stream_holder,
499
+ )
500
+
501
+ # Empty UST data.
502
+ target._pos = self._pos.empty_like(stream_holder)
503
+ target._crd = self._crd.empty_like(stream_holder)
504
+ target._val = utils.create_empty_tensor(
505
+ self._dense_tensorholder_type, self.nse, self.dtype, self.device_id, stream_holder, verify_strides=False
506
+ )
507
+
508
+ # Copy the UST data.
509
+ target._pos.copy_(self._pos, stream_holder)
510
+ target._crd.copy_(self._crd, stream_holder)
511
+ target._val.copy_(self._val, stream_holder)
512
+
513
+ target._kernel = self._kernel # not deeply cloned
514
+ target._with_indices = self._with_indices
515
+
516
+ # Set wrapped operand.
517
+ if target._is_dense_format():
518
+ target._wrapped_operand = target._dense_tensor_from_buffer()
519
+ else:
520
+ target._wrapped_operand = wrap_sparse_operand(target)
521
+
522
+ return target
523
+
524
+ def set_kernel(self, user_code, *, with_indices=True, arch=None):
525
+ """
526
+ Set the callback code (a unary Python function, possibly already compiled into
527
+ LTO-IR) and link this with the actual traversal kernel for the UST. This can be
528
+ used to apply an user-defined transformation to each element of the UST, where the
529
+ user-defined transformation can potentially depend on the coordinates (indices)
530
+ of the element.
531
+
532
+ Args:
533
+ user_code: The callback code as a unary Python function object or as a bytes
534
+ object with the LTO-IR code. The function must take a single argument
535
+ representing the original value if ``with_indices`` is ``False``, otherwise
536
+ it takes the original value followed by the coordinate for that value, as a
537
+ sequence. The original value must be representable in the data type of the
538
+ UST, while the indices must be integers.
539
+
540
+ with_indices: A flag to choose between the value-only or value-with-indices
541
+ signatures.
542
+
543
+ .. tip:: If the indices (coordinate) are not needed for the user-defined
544
+ transformation, it is much more efficient to set ``with_indices=False``.
545
+ """
546
+
547
+ if self._val.device == "cpu":
548
+ raise ValueError("A CUDA kernel cannot be specified for the CPU memory space.")
549
+
550
+ if not isinstance(user_code, bytes):
551
+ signature = self.dtype, self.dtype # return type, value dtype
552
+ if with_indices:
553
+ signature += (self.index_type,) * self.num_dimensions
554
+ user_code = compile_python_function(user_code, "apply", signature)
555
+
556
+ driver_src, grid_iter = emit_apply_kernel(self, with_indices)
557
+ complex_path = pathfinder.find_nvidia_header_directory("cccl")
558
+ narrow_prec_path = pathfinder.find_nvidia_header_directory("cudart")
559
+ compiler_options = {
560
+ "std": "c++17",
561
+ "link_time_optimization": True,
562
+ "arch": arch,
563
+ "include_path": [complex_path, narrow_prec_path],
564
+ }
565
+ linker_options = {"link_time_optimization": True}
566
+
567
+ self._kernel = compile_cpp_and_link(
568
+ src_code=driver_src,
569
+ object_code=user_code,
570
+ function_name="apply_kernel",
571
+ compiler_options=compiler_options,
572
+ linker_options=linker_options,
573
+ )
574
+ self._grid_iter = grid_iter
575
+ self._with_indices = with_indices
576
+
577
+ def _check_valid_kernel(self, *args, **kwargs):
578
+ """
579
+ Check if a kernel has been set.
580
+ """
581
+ if not self._kernel:
582
+ raise ValueError("A kernel has not been set using `set_kernel`.")
583
+
584
+ @utils.precondition(_check_valid_kernel)
585
+ def run_kernel(self, stream=None):
586
+ """
587
+ Run the kernel associated with this UST object.
588
+ """
589
+
590
+ assert self._kernel is not None, "Internal error."
591
+ stream_holder = resolve_stream(stream, self)
592
+
593
+ (grid, block), params = populate_apply_parameters(
594
+ self, self._with_indices, self._grid_iter, stream_holder=stream_holder
595
+ )
596
+
597
+ device_id = self._val.device_id
598
+ launch_kernel(self._kernel, grid, block, params, device_id=device_id, stream_holder=stream_holder)
599
+
600
+ def get_value(self, indices, stream=None):
601
+ """
602
+ Retrieve the value at specified indices (coordinate) or ``None`` if there is no
603
+ value at that coordinate. This is *not* random access, and searches compressed
604
+ levels.
605
+
606
+ Args:
607
+ indices: The logical dimension indices of the element to retrieve.
608
+ stream: {stream}
609
+
610
+ Returns:
611
+ ``tensor.get_value[i, j, k, ...]` returns the value of the element appearing at
612
+ logical dimension indices ``i, j, k, ...` or ``None`` if there is no element at
613
+ that coordinate.
614
+ """
615
+ if len(indices) != self.num_dimensions:
616
+ raise ValueError(
617
+ f"The number of indices specified {indices} doesn't match the number of \
618
+ tensor dimensions {self.num_dimensions}."
619
+ )
620
+ stream_holder = resolve_stream(stream, self)
621
+ with stream_context(stream_holder):
622
+ p = self._locate(list(self.tensor_format.levels.values()), self.tensor_format.dim2lvl(indices), 0, 0)
623
+ return p if p is None else self._val.tensor[p].item()
624
+
625
+ def draw(self, name=None, stream=None):
626
+ """
627
+ Draws the tensor contents (for 1D, 2D, 3D). The method saves the constructed
628
+ :class:`PIL.Image` in a file named `name` when name is not None. It always
629
+ returns the :class:`PIL.Image` object directly as well, so that the caller
630
+ can directly manipulate the constructed image in some other manner.
631
+
632
+ This method is useful to illustrate tensor contents of smaller examples.
633
+
634
+ Args:
635
+ name: filename to save the image to, if not None
636
+ stream: {stream}
637
+ Returns:
638
+ PIL.Image, can be displayed with show()
639
+
640
+ Examples:
641
+ >>> import scipy.sparse as sp
642
+ >>> from nvmath.sparse.ust import Tensor
643
+
644
+ Create a sparse scipy matrix in CSR format.
645
+
646
+ >>> a = sp.random_array((8, 8), density=0.1, format="csr", dtype="float64")
647
+
648
+ Convert the scipy matrix to UST.
649
+
650
+ >>> u = Tensor.from_package(a)
651
+
652
+ Obtain the tensor contents as image (and e.g. display as ``img.show()``).
653
+
654
+ >>> img = u.draw() # doctest: +SKIP
655
+ """
656
+ return draw_tensor(self, name, stream=stream)
657
+
658
+ def draw_storage(self, name=None, stream=None):
659
+ """
660
+ Draws the tensor storage (for any UST). The method saves the constructed
661
+ :class:`PIL.Image` in a file named `name` when name is not None. It always
662
+ returns the :class:`PIL.Image` object directly as well, so that the caller
663
+ can directly manipulate the constructed image in some other manner.
664
+
665
+ This method is useful to illustrate the UST storage of smaller examples.
666
+
667
+ Args:
668
+ name: filename to save the image to, if not None
669
+ stream: {stream}
670
+ Returns:
671
+ PIL.Image, can be displayed with show()
672
+
673
+ Examples:
674
+ >>> import scipy.sparse as sp
675
+ >>> from nvmath.sparse.ust import Tensor
676
+
677
+ Create a sparse scipy matrix in CSR format.
678
+
679
+ >>> a = sp.random_array((8, 8), density=0.1, format="csr", dtype="float64")
680
+
681
+ Convert the scipy matrix to UST.
682
+
683
+ >>> u = Tensor.from_package(a)
684
+
685
+ Obtain the tensor storage as image (and e.g. display as ``img.show()``).
686
+
687
+ >>> img = u.draw_storage() # doctest: +SKIP
688
+ """
689
+ return draw_tensor_storage(self, name, stream=stream)
690
+
691
+ def draw_raw(self, name=None, stream=None):
692
+ """
693
+ Draws the tensor nonzero structure (for 2D, 3D). The method saves the constructed
694
+ :class:`PIL.Image` in a file named `name` when name is not None. It always
695
+ returns the :class:`PIL.Image` object directly as well, so that the caller
696
+ can directly manipulate the constructed image in some other manner.
697
+
698
+ This method scales to larger tensors.
699
+
700
+ Args:
701
+ name: filename to save the image to, if not None
702
+ stream: {stream}
703
+ Returns:
704
+ PIL.Image, can be displayed with show()
705
+
706
+ Examples:
707
+ >>> import scipy.sparse as sp
708
+ >>> from nvmath.sparse.ust import Tensor
709
+
710
+ Create a sparse scipy matrix in CSR format.
711
+
712
+ >>> a = sp.random_array((512, 512), density=0.01, format="csr", dtype="float64")
713
+
714
+ Convert the scipy matrix to UST.
715
+
716
+ >>> u = Tensor.from_package(a)
717
+
718
+ Obtain the tensor nonzero structure as image (e.g. display as ``img.show()``).
719
+
720
+ >>> img = u.draw_raw() # doctest: +SKIP
721
+ """
722
+ return draw_tensor_raw(self, name, stream=stream)
723
+
724
+ def animate(self, name=None, stream=None):
725
+ """
726
+ Animates the tensor nonzero structure (for 3D). The method either saves the
727
+ animated GIF in a file named `name` when name is not None, or otherwise
728
+ returns a :class:`IPython.display.HTML` that can be embedded by the caller.
729
+
730
+ This method scales to larger tensors.
731
+
732
+ Args:
733
+ name: filename to save the animated GIF to, if not None
734
+ stream: {stream}
735
+ Returns:
736
+ IPython.display.HTML, if name is None (to embed in other output)
737
+
738
+ Examples:
739
+ >>> import torch
740
+ >>> from nvmath.sparse.ust import Tensor
741
+
742
+ Create a 3-D tensor, as COO.
743
+
744
+ >>> a = torch.eye(32).repeat(32, 1, 2).to_sparse()
745
+
746
+ Convert the tensor to UST.
747
+
748
+ >>> u = Tensor.from_package(a)
749
+
750
+ Obtain the animation as HTML.
751
+
752
+ >>> html = u.animate() # doctest: +SKIP
753
+ """
754
+ return animate_tensor(self, name, stream=stream)
755
+
756
+ @property
757
+ def extents(self):
758
+ """
759
+ The extents (shape) of this UST object.
760
+ """
761
+ return self._extents
762
+
763
+ shape = extents
764
+
765
+ @property
766
+ def levels(self):
767
+ """
768
+ The extents corresponding to each level for this UST object.
769
+ """
770
+ return self._levels
771
+
772
+ @property
773
+ def size(self):
774
+ """
775
+ The envelope of this UST object (product of the extents).
776
+ """
777
+ # TODO: rename this to envelope or bounding_volume?
778
+ return np.prod(self.extents)
779
+
780
+ @property
781
+ def num_dimensions(self):
782
+ """
783
+ The number of dimensions of this UST object.
784
+ """
785
+ return self.tensor_format.num_dimensions
786
+
787
+ @property
788
+ def num_levels(self):
789
+ """
790
+ The number of levels of this UST object.
791
+ """
792
+ return self.tensor_format.num_levels
793
+
794
+ @property
795
+ def tensor_format(self):
796
+ """
797
+ The UST format as a :class:`nvmath.sparse.ust.TensorFormat` object.
798
+ """
799
+ return self._tensor_format
800
+
801
+ @property
802
+ def index_type(self):
803
+ """
804
+ The index type of this UST object as a string.
805
+ """
806
+ return self._index_type
807
+
808
+ @property
809
+ def dtype(self):
810
+ """
811
+ The data type of this UST object as a string.
812
+ """
813
+ return self._dtype
814
+
815
+ def pos(self, level):
816
+ """
817
+ The positions corresponding to the specified level.
818
+
819
+ Args:
820
+ level: An ordinal specifying the level.
821
+ """
822
+ holder = self._pos.get(level, None)
823
+ return None if holder is None else holder.tensor
824
+
825
+ def crd(self, level):
826
+ """
827
+ The coordinates corresponding to the specified level.
828
+
829
+ Args:
830
+ level: An ordinal specifying the level.
831
+ """
832
+ holder = self._crd.get(level, None)
833
+ return None if holder is None else holder.tensor
834
+
835
+ @property
836
+ def val(self):
837
+ """
838
+ The explicit values stored in this UST object.
839
+ """
840
+ return [] if self._val is None else self._val.tensor
841
+
842
+ @property
843
+ def device(self):
844
+ """
845
+ The memory space of this UST object ("cpu" or "cuda").
846
+ """
847
+ return "cpu" if self._val is None else self._val.device
848
+
849
+ @property
850
+ def device_id(self):
851
+ """
852
+ The device ID of this UST object (CUDA ordinal or "cpu").
853
+ """
854
+ return "cpu" if self._val is None else self._val.device_id
855
+
856
+ @property
857
+ def nse(self):
858
+ """
859
+ The number of stored elements in this UST object.
860
+ """
861
+ return 0 if self._val is None else self._val.size
862
+
863
+ @property
864
+ def base(self):
865
+ """
866
+ Get the "package" tensor from which the UST view was created, if available.
867
+ """
868
+ if self._wrapped_operand is not None:
869
+ return self._wrapped_operand.tensor
870
+
871
+ @property
872
+ def _dense_tensorholder_type(self):
873
+ """
874
+ (Internal use only) The tensor holder type for the wrapped dense tensors.
875
+ """
876
+ return self._val.__class__
877
+
878
+ def __repr__(self):
879
+ s = (
880
+ f"---- Sparse Tensor<VAL={self.dtype},"
881
+ f"POS={self.index_type},CRD={self.index_type},"
882
+ f"DIM={self.num_dimensions},LVL={self.num_levels}>\n"
883
+ )
884
+ s += f"format : {self.tensor_format}\n"
885
+ s += f"device : {self.device}\n"
886
+ s += f"dim : {self.extents}\n"
887
+ s += f"lvl : {self.levels}\n"
888
+ s += f"nse : {self.nse}\n"
889
+ data = 0
890
+ for level in range(self.num_levels):
891
+ pos = self._pos.get(level)
892
+ if pos is None:
893
+ continue
894
+ s += f"pos[{level}] : {_tensor_to_list(pos)} #{pos.size}\n"
895
+ data += pos.size * pos.itemsize
896
+ for level in range(self.num_levels):
897
+ crd = self._crd.get(level)
898
+ if crd is None:
899
+ continue
900
+ s += f"crd[{level}] : {_tensor_to_list(crd)} #{crd.size}\n"
901
+ data += crd.size * crd.itemsize
902
+ if self.nse > 0:
903
+ val = self._val
904
+ s += f"values : {_tensor_to_list(val)} #{val.size}\n"
905
+ data += val.size * val.itemsize
906
+ s += f"data : {data} bytes\n"
907
+ s += f"sparsity : {(100.0 - (100.0 * self.nse) / self.size):.2f}%\n"
908
+ s += "----"
909
+ return s
910
+
911
+ def _locate(self, formats, lvls, level, p):
912
+ # Exhausted all levels.
913
+ if level == self.num_levels:
914
+ return p
915
+ # Obtain format and properties at current level.
916
+ v = formats[level]
917
+ if isinstance(v, tuple):
918
+ fmt, prop = v
919
+ else:
920
+ fmt, prop = v, None
921
+ # Handle level format.
922
+ if fmt == LevelFormat.DENSE or fmt == LevelFormat.BATCH or fmt == LevelFormat.RANGE:
923
+ return self._locate(formats, lvls, level + 1, p * self.levels[level] + lvls[level])
924
+ elif fmt == LevelFormat.COMPRESSED:
925
+ unique = is_unique(prop)
926
+ pos = self._pos[level].tensor
927
+ crd = self._crd[level].tensor
928
+ assert pos.ndim == crd.ndim
929
+ adjust = 0
930
+ if pos.ndim > 1:
931
+ # This only happens for prior BATCH. Correct the higher-dimensions in
932
+ # both pos and crd buffers and adjust position by #nnz per batch.
933
+ assert pos.ndim == level
934
+ for i in range(level - 1):
935
+ assert formats[i] == LevelFormat.BATCH
936
+ pos = pos[lvls[i]]
937
+ crd = crd[lvls[i]]
938
+ adjust = pos[-1].item() * (p - lvls[level - 1]) // self.levels[level - 1]
939
+ p = lvls[level - 1]
940
+ lo = pos[p].item()
941
+ hi = pos[p + 1].item()
942
+ for i in range(lo, hi):
943
+ if crd[i].item() == lvls[level]:
944
+ cpos = self._locate(formats, lvls, level + 1, i + adjust)
945
+ if unique:
946
+ return cpos # always end scan (unique)
947
+ elif cpos is not None:
948
+ return cpos # only end scan on success (non-unique)
949
+ elif fmt == LevelFormat.SINGLETON:
950
+ crd = self._crd[level].tensor
951
+ assert crd.ndim == 1
952
+ if crd[p].item() == lvls[level]:
953
+ return self._locate(formats, lvls, level + 1, p)
954
+ elif fmt == LevelFormat.DELTA:
955
+ assert is_unique(prop)
956
+ pos = self._pos[level].tensor
957
+ crd = self._crd[level].tensor
958
+ assert pos.ndim == 1 and crd.ndim == 1
959
+ corig = 0
960
+ lo = pos[p].item()
961
+ hi = pos[p + 1].item()
962
+ for i in range(lo, hi):
963
+ corig += crd[i].item()
964
+ if corig == lvls[level]:
965
+ return self._locate(formats, lvls, level + 1, i)
966
+ corig += 1
967
+ elif fmt == LevelFormat.STRUCTURED:
968
+ assert isinstance(prop, int) # n is the property
969
+ crd = self._crd[level].tensor
970
+ assert crd.ndim == 1
971
+ lo = p * prop
972
+ hi = lo + prop
973
+ for i in range(lo, hi):
974
+ if crd[i] == lvls[level]:
975
+ return self._locate(formats, lvls, level + 1, i)
976
+ else:
977
+ raise AssertionError(f"Unsupported: {fmt}")
978
+ return None
979
+
980
+ @classmethod
981
+ def _from_dense(cls, operand):
982
+ # Wrap operand to canonicalize attributes.
983
+ operand = tensor_wrapper.wrap_operand(operand)
984
+
985
+ if not is_contiguous_and_dense(operand.shape, operand.strides):
986
+ raise NotImplementedError("A UST cannot be currently created from strided tensors with holes (that is, not dense).")
987
+
988
+ # Axis order, to determine UST dense format.
989
+ axis_order = _axis_order_in_memory(operand.shape, operand.strides)
990
+ num_dimensions = len(axis_order)
991
+ dimensions = [Dimension(dimension_name=f"{chr(ord('i') + d)}") for d in range(num_dimensions)]
992
+ levels = {dimensions[d]: LevelFormat.DENSE for d in reversed(axis_order)}
993
+
994
+ # UST format.
995
+ if num_dimensions == 0:
996
+ name = "Scalar"
997
+ elif num_dimensions == 1:
998
+ name = "DenseVector"
999
+ elif num_dimensions == 2:
1000
+ name = "Densed" + ("Left" if axis_order == (0, 1) else "Right")
1001
+ else:
1002
+ name = f"Dense{num_dimensions}D-" + "-".join(str(d) for d in reversed(axis_order))
1003
+ tensor_format = TensorFormat(dimensions, levels, name=name)
1004
+
1005
+ # UST data.
1006
+ index_type = _get_smallest_index_type(max(operand.shape) if num_dimensions > 0 else 0)
1007
+ values = operand.memory_buffer()
1008
+ dtype = values.dtype
1009
+
1010
+ # Create UST.
1011
+ ust = cls(operand.shape, tensor_format=tensor_format, index_type=index_type, dtype=dtype)
1012
+ ust._val = values
1013
+
1014
+ ust._wrapped_operand = operand
1015
+
1016
+ return ust