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