torchsparsegradutils 0.1.2__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. torchsparsegradutils-0.2.0/PKG-INFO +705 -0
  2. torchsparsegradutils-0.2.0/README.md +661 -0
  3. torchsparsegradutils-0.2.0/pyproject.toml +49 -0
  4. {torchsparsegradutils-0.1.2 → torchsparsegradutils-0.2.0}/setup.py +15 -5
  5. torchsparsegradutils-0.2.0/torchsparsegradutils/__init__.py +13 -0
  6. torchsparsegradutils-0.2.0/torchsparsegradutils/benchmarks/__init__.py +10 -0
  7. torchsparsegradutils-0.2.0/torchsparsegradutils/benchmarks/batched_sparse_mm_rand.py +444 -0
  8. torchsparsegradutils-0.2.0/torchsparsegradutils/benchmarks/benchmark_suite.py +77 -0
  9. torchsparsegradutils-0.2.0/torchsparsegradutils/benchmarks/benchmark_utils.py +404 -0
  10. torchsparsegradutils-0.2.0/torchsparsegradutils/benchmarks/sparse_generic_solve_rand.py +309 -0
  11. torchsparsegradutils-0.2.0/torchsparsegradutils/benchmarks/sparse_generic_solve_suite.py +273 -0
  12. torchsparsegradutils-0.2.0/torchsparsegradutils/benchmarks/sparse_mm_rand.py +168 -0
  13. torchsparsegradutils-0.2.0/torchsparsegradutils/benchmarks/sparse_mm_suite.py +160 -0
  14. torchsparsegradutils-0.2.0/torchsparsegradutils/benchmarks/sparse_triangular_solve_rand.py +244 -0
  15. torchsparsegradutils-0.2.0/torchsparsegradutils/benchmarks/sparse_triangular_solve_suitesparse.py +264 -0
  16. torchsparsegradutils-0.2.0/torchsparsegradutils/benchmarks/visualize_benchmark_results.py +1049 -0
  17. {torchsparsegradutils-0.1.2 → torchsparsegradutils-0.2.0}/torchsparsegradutils/cupy/__init__.py +1 -1
  18. torchsparsegradutils-0.2.0/torchsparsegradutils/cupy/cupy_bindings.py +245 -0
  19. torchsparsegradutils-0.2.0/torchsparsegradutils/cupy/cupy_sparse_solve.py +422 -0
  20. torchsparsegradutils-0.2.0/torchsparsegradutils/distributions/__init__.py +3 -0
  21. torchsparsegradutils-0.2.0/torchsparsegradutils/distributions/sparse_multivariate_normal.py +589 -0
  22. torchsparsegradutils-0.2.0/torchsparsegradutils/encoders/__init__.py +14 -0
  23. torchsparsegradutils-0.2.0/torchsparsegradutils/encoders/pairwise_encoder.py +845 -0
  24. torchsparsegradutils-0.2.0/torchsparsegradutils/encoders/pairwise_voxel_encoder.py +125 -0
  25. torchsparsegradutils-0.2.0/torchsparsegradutils/indexed_matmul.py +217 -0
  26. {torchsparsegradutils-0.1.2 → torchsparsegradutils-0.2.0}/torchsparsegradutils/jax/__init__.py +6 -2
  27. torchsparsegradutils-0.2.0/torchsparsegradutils/jax/jax_bindings.py +313 -0
  28. torchsparsegradutils-0.2.0/torchsparsegradutils/jax/jax_sparse_solve.py +258 -0
  29. torchsparsegradutils-0.2.0/torchsparsegradutils/sparse_lstsq.py +271 -0
  30. torchsparsegradutils-0.2.0/torchsparsegradutils/sparse_matmul.py +234 -0
  31. torchsparsegradutils-0.2.0/torchsparsegradutils/sparse_solve.py +514 -0
  32. torchsparsegradutils-0.2.0/torchsparsegradutils/tests/test_bicgstab.py +61 -0
  33. torchsparsegradutils-0.2.0/torchsparsegradutils/tests/test_cupy_bindings.py +123 -0
  34. torchsparsegradutils-0.2.0/torchsparsegradutils/tests/test_cupy_sparse_solve.py +278 -0
  35. torchsparsegradutils-0.2.0/torchsparsegradutils/tests/test_dist_stats_helpers.py +321 -0
  36. torchsparsegradutils-0.2.0/torchsparsegradutils/tests/test_distributions.py +595 -0
  37. torchsparsegradutils-0.2.0/torchsparsegradutils/tests/test_doctests.py +73 -0
  38. {torchsparsegradutils-0.1.2 → torchsparsegradutils-0.2.0}/torchsparsegradutils/tests/test_encoders.py +230 -16
  39. torchsparsegradutils-0.2.0/torchsparsegradutils/tests/test_indexed_matmul.py +94 -0
  40. torchsparsegradutils-0.2.0/torchsparsegradutils/tests/test_integration_pairwise_sparse_mvn.py +761 -0
  41. torchsparsegradutils-0.2.0/torchsparsegradutils/tests/test_jax_bindings.py +123 -0
  42. torchsparsegradutils-0.2.0/torchsparsegradutils/tests/test_jax_sparse_solve.py +223 -0
  43. torchsparsegradutils-0.2.0/torchsparsegradutils/tests/test_linear_cg.py +123 -0
  44. torchsparsegradutils-0.2.0/torchsparsegradutils/tests/test_lsmr.py +255 -0
  45. torchsparsegradutils-0.2.0/torchsparsegradutils/tests/test_minres.py +72 -0
  46. torchsparsegradutils-0.2.0/torchsparsegradutils/tests/test_quickstart_guide.py +189 -0
  47. torchsparsegradutils-0.2.0/torchsparsegradutils/tests/test_random.py +923 -0
  48. torchsparsegradutils-0.2.0/torchsparsegradutils/tests/test_sparse_lstsq.py +242 -0
  49. torchsparsegradutils-0.2.0/torchsparsegradutils/tests/test_sparse_matmul.py +396 -0
  50. torchsparsegradutils-0.2.0/torchsparsegradutils/tests/test_sparse_solve.py +271 -0
  51. torchsparsegradutils-0.2.0/torchsparsegradutils/tests/test_sparse_triangular_solve.py +305 -0
  52. torchsparsegradutils-0.2.0/torchsparsegradutils/tests/test_utils.py +290 -0
  53. {torchsparsegradutils-0.1.2 → torchsparsegradutils-0.2.0}/torchsparsegradutils/utils/__init__.py +9 -6
  54. torchsparsegradutils-0.2.0/torchsparsegradutils/utils/bicgstab.py +247 -0
  55. torchsparsegradutils-0.2.0/torchsparsegradutils/utils/dist_stats_helpers.py +373 -0
  56. {torchsparsegradutils-0.1.2 → torchsparsegradutils-0.2.0}/torchsparsegradutils/utils/linear_cg.py +117 -38
  57. {torchsparsegradutils-0.1.2 → torchsparsegradutils-0.2.0}/torchsparsegradutils/utils/lsmr.py +122 -59
  58. {torchsparsegradutils-0.1.2 → torchsparsegradutils-0.2.0}/torchsparsegradutils/utils/minres.py +123 -28
  59. torchsparsegradutils-0.2.0/torchsparsegradutils/utils/random_sparse.py +1371 -0
  60. torchsparsegradutils-0.2.0/torchsparsegradutils/utils/utils.py +914 -0
  61. torchsparsegradutils-0.2.0/torchsparsegradutils.egg-info/PKG-INFO +705 -0
  62. {torchsparsegradutils-0.1.2 → torchsparsegradutils-0.2.0}/torchsparsegradutils.egg-info/SOURCES.txt +20 -0
  63. torchsparsegradutils-0.2.0/torchsparsegradutils.egg-info/requires.txt +15 -0
  64. torchsparsegradutils-0.1.2/PKG-INFO +0 -58
  65. torchsparsegradutils-0.1.2/README.md +0 -39
  66. torchsparsegradutils-0.1.2/pyproject.toml +0 -24
  67. torchsparsegradutils-0.1.2/torchsparsegradutils/__init__.py +0 -5
  68. torchsparsegradutils-0.1.2/torchsparsegradutils/cupy/cupy_bindings.py +0 -77
  69. torchsparsegradutils-0.1.2/torchsparsegradutils/cupy/cupy_sparse_solve.py +0 -96
  70. torchsparsegradutils-0.1.2/torchsparsegradutils/distributions/__init__.py +0 -3
  71. torchsparsegradutils-0.1.2/torchsparsegradutils/distributions/sparse_multivariate_normal.py +0 -198
  72. torchsparsegradutils-0.1.2/torchsparsegradutils/encoders/__init__.py +0 -3
  73. torchsparsegradutils-0.1.2/torchsparsegradutils/encoders/pairwise_voxel_encoder.py +0 -511
  74. torchsparsegradutils-0.1.2/torchsparsegradutils/jax/jax_bindings.py +0 -80
  75. torchsparsegradutils-0.1.2/torchsparsegradutils/jax/jax_sparse_solve.py +0 -90
  76. torchsparsegradutils-0.1.2/torchsparsegradutils/sparse_lstsq.py +0 -136
  77. torchsparsegradutils-0.1.2/torchsparsegradutils/sparse_matmul.py +0 -130
  78. torchsparsegradutils-0.1.2/torchsparsegradutils/sparse_solve.py +0 -303
  79. torchsparsegradutils-0.1.2/torchsparsegradutils/tests/test_bicgstab.py +0 -60
  80. torchsparsegradutils-0.1.2/torchsparsegradutils/tests/test_cupy_bindings.py +0 -96
  81. torchsparsegradutils-0.1.2/torchsparsegradutils/tests/test_cupy_sparse_solve.py +0 -86
  82. torchsparsegradutils-0.1.2/torchsparsegradutils/tests/test_distributions.py +0 -247
  83. torchsparsegradutils-0.1.2/torchsparsegradutils/tests/test_jax_bindings.py +0 -110
  84. torchsparsegradutils-0.1.2/torchsparsegradutils/tests/test_jax_sparse_solve.py +0 -77
  85. torchsparsegradutils-0.1.2/torchsparsegradutils/tests/test_linear_cg.py +0 -131
  86. torchsparsegradutils-0.1.2/torchsparsegradutils/tests/test_lsmr.py +0 -315
  87. torchsparsegradutils-0.1.2/torchsparsegradutils/tests/test_minres.py +0 -113
  88. torchsparsegradutils-0.1.2/torchsparsegradutils/tests/test_random.py +0 -476
  89. torchsparsegradutils-0.1.2/torchsparsegradutils/tests/test_sparse_lstsq.py +0 -78
  90. torchsparsegradutils-0.1.2/torchsparsegradutils/tests/test_sparse_matmul.py +0 -187
  91. torchsparsegradutils-0.1.2/torchsparsegradutils/tests/test_sparse_solve.py +0 -598
  92. torchsparsegradutils-0.1.2/torchsparsegradutils/tests/test_utils.py +0 -705
  93. torchsparsegradutils-0.1.2/torchsparsegradutils/utils/bicgstab.py +0 -187
  94. torchsparsegradutils-0.1.2/torchsparsegradutils/utils/random_sparse.py +0 -367
  95. torchsparsegradutils-0.1.2/torchsparsegradutils/utils/utils.py +0 -480
  96. torchsparsegradutils-0.1.2/torchsparsegradutils.egg-info/PKG-INFO +0 -58
  97. torchsparsegradutils-0.1.2/torchsparsegradutils.egg-info/requires.txt +0 -5
  98. {torchsparsegradutils-0.1.2 → torchsparsegradutils-0.2.0}/LICENSE +0 -0
  99. {torchsparsegradutils-0.1.2 → torchsparsegradutils-0.2.0}/MANIFEST.in +0 -0
  100. {torchsparsegradutils-0.1.2 → torchsparsegradutils-0.2.0}/setup.cfg +0 -0
  101. {torchsparsegradutils-0.1.2 → torchsparsegradutils-0.2.0}/torchsparsegradutils/distributions/constraints.py +0 -0
  102. {torchsparsegradutils-0.1.2 → torchsparsegradutils-0.2.0}/torchsparsegradutils/tests/__init__.py +0 -0
  103. {torchsparsegradutils-0.1.2 → torchsparsegradutils-0.2.0}/torchsparsegradutils/tests/test_params/czyx_shifts.yaml +0 -0
  104. {torchsparsegradutils-0.1.2 → torchsparsegradutils-0.2.0}/torchsparsegradutils/tests/test_params/pairwise_coo_indices.yaml +0 -0
  105. {torchsparsegradutils-0.1.2 → torchsparsegradutils-0.2.0}/torchsparsegradutils/tests/test_params/xyz_coords.yaml +0 -0
  106. {torchsparsegradutils-0.1.2 → torchsparsegradutils-0.2.0}/torchsparsegradutils.egg-info/dependency_links.txt +0 -0
  107. {torchsparsegradutils-0.1.2 → torchsparsegradutils-0.2.0}/torchsparsegradutils.egg-info/not-zip-safe +0 -0
  108. {torchsparsegradutils-0.1.2 → torchsparsegradutils-0.2.0}/torchsparsegradutils.egg-info/top_level.txt +0 -0
@@ -0,0 +1,705 @@
1
+ Metadata-Version: 2.4
2
+ Name: torchsparsegradutils
3
+ Version: 0.2.0
4
+ Summary: A collection of utility functions to work with PyTorch sparse tensors
5
+ Home-page: https://github.com/cai4cai/torchsparsegradutils
6
+ Author: CAI4CAI research group
7
+ Author-email: contact@cai4cai.uk
8
+ License: Apache-2.0
9
+ Keywords: sparse torch utility
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: License :: OSI Approved :: Apache Software License
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: torch>=2.5
19
+ Requires-Dist: scipy
20
+ Provides-Extra: extras
21
+ Requires-Dist: jax; extra == "extras"
22
+ Requires-Dist: cupy; extra == "extras"
23
+ Provides-Extra: docs
24
+ Requires-Dist: sphinx>=7.0.0; extra == "docs"
25
+ Requires-Dist: sphinx-rtd-theme>=1.3.0; extra == "docs"
26
+ Requires-Dist: sphinx-copybutton>=0.5.0; extra == "docs"
27
+ Requires-Dist: myst-parser>=2.0.0; extra == "docs"
28
+ Requires-Dist: sphinx-autobuild>=2021.3.14; extra == "docs"
29
+ Requires-Dist: matplotlib>=3.5.0; extra == "docs"
30
+ Requires-Dist: sphinx-autodoc-typehints>=1.24.0; extra == "docs"
31
+ Dynamic: author
32
+ Dynamic: author-email
33
+ Dynamic: classifier
34
+ Dynamic: description
35
+ Dynamic: description-content-type
36
+ Dynamic: home-page
37
+ Dynamic: keywords
38
+ Dynamic: license
39
+ Dynamic: license-file
40
+ Dynamic: provides-extra
41
+ Dynamic: requires-dist
42
+ Dynamic: requires-python
43
+ Dynamic: summary
44
+
45
+ # torchsparsegradutils: Sparsity-preserving gradient utility tools for PyTorch
46
+
47
+ [![Python tests](https://github.com/cai4cai/torchsparsegradutils/actions/workflows/python-package.yml/badge.svg)](https://github.com/cai4cai/torchsparsegradutils/actions/workflows/python-package.yml) [![License](https://img.shields.io/github/license/cai4cai/torchsparsegradutils)](https://github.com/cai4cai/torchsparsegradutils?tab=Apache-2.0-1-ov-file#readme) [![Code Style: Black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
48
+
49
+ A comprehensive collection of utility functions to work with PyTorch sparse tensors, ensuring memory efficiency and supporting various sparsity-preserving tensor operations with automatic differentiation. This package addresses fundamental gaps in PyTorch's sparse tensor ecosystem, providing essential operations that preserve sparsity in gradients during backpropagation.
50
+
51
+ ## 🚀 Key Features
52
+
53
+ ### Core Sparse Operations with Sparse Gradient Support
54
+
55
+ **Memory-Efficient Sparse Matrix Multiplication**
56
+ - `sparse_mm`: Memory-efficient sparse matrix multiplication with batch support
57
+ - Preserves sparsity in gradients during backpropagation
58
+ - Workaround for [PyTorch issue #41128](https://github.com/pytorch/pytorch/issues/41128)
59
+ - Supports both COO and CSR formats with optional batching
60
+
61
+ **Sparse Linear System Solvers**
62
+ - `sparse_triangular_solve`: Sparse triangular solver with batch support
63
+ - Discussion reference: [PyTorch issue #87358](https://github.com/pytorch/pytorch/issues/87358)
64
+ - `sparse_generic_solve`: Generic sparse linear solver with pluggable backends
65
+ - Tested and benchmarked with CG, BICGSTAB, LSMR and MINRES solvers
66
+
67
+ - `sparse_solve_c4t`: Wrappers around [cupy sparse solvers](https://docs.cupy.dev/en/stable/reference/scipy_sparse_linalg.html#solving-linear-problems)
68
+ - Discussion reference: [Pytorch issue #69538](https://github.com/pytorch/pytorch/issues/69538)
69
+ - Tested and benchmarked with: [CG](https://docs.cupy.dev/en/v9.6.0/reference/generated/cupyx.scipy.sparse.linalg.cg.html), [CGS](https://docs.cupy.dev/en/stable/reference/generated/cupyx.scipy.sparse.linalg.cgs.html#cupyx.scipy.sparse.linalg.cgs), [MINRES](https://docs.cupy.dev/en/stable/reference/generated/cupyx.scipy.sparse.linalg.minres.html#cupyx.scipy.sparse.linalg.minres), [GMRES](https://docs.cupy.dev/en/stable/reference/generated/cupyx.scipy.sparse.linalg.gmres.html#cupyx.scipy.sparse.linalg.gmres), [spsolve](https://docs.cupy.dev/en/stable/reference/generated/cupyx.scipy.sparse.linalg.spsolve.html#cupyx.scipy.sparse.linalg.spsolve) and [spsolve_triangular](https://docs.cupy.dev/en/stable/reference/generated/cupyx.scipy.sparse.linalg.spsolve_triangular.html#cupyx.scipy.sparse.linalg.spsolve_triangular) CuPy solvers
70
+ - `tsgujax.sparse_solve_j4t`: Wrappers around [jax sparse solvers](https://jax.readthedocs.io/en/latest/jax.scipy.html#module-jax.scipy.sparse.linalg)
71
+ - Tested with: CG and BICGSTAB JAX solvers
72
+ - `sparse_generic_lstsq`: Generic sparse linear least-squares solver
73
+
74
+ ### Built-in Iterative Solvers (No External Dependencies)
75
+
76
+ **Pure PyTorch Implementations**
77
+ - **BICGSTAB**: Biconjugate Gradient Stabilized method (ported from [pykrylov](https://github.com/PythonOptimizers/pykrylov))
78
+ - **CG**: Conjugate Gradient method (ported from [cornellius-gp/linear_operator](https://github.com/cornellius-gp/linear_operator))
79
+ - **LSMR**: Least Squares Minimal Residual method (ported from [pytorch-minimize](https://github.com/rfeinman/pytorch-minimize))
80
+ - **MINRES**: Minimal Residual method (ported from [cornellius-gp/linear_operator](https://github.com/cornellius-gp/linear_operator))
81
+
82
+ ### Sparse Multivariate Normal Distributions
83
+
84
+ - **SparseMultivariateNormal**: Structured Gaussian Distribution
85
+ - Implements reparameterised sampling (rsample)
86
+ - Supports leading batch dimension
87
+ - Supports COO and CSR sparse tensors
88
+ - Covariance or precision matrices with LL^T or LDL^T parameterisations.
89
+ - LDL^T parameterization offers numerical stability without SPD constraints
90
+ - **SparseMultivariateNormalNative**:
91
+ - Implements reparameterised sampling (rsample)
92
+ - Uses native `torch.sparse.mm` only
93
+ - Only supports ubatched CSR tensors
94
+ - Covariance LL^T parameterization
95
+
96
+ ### Spatial Encoding Tools
97
+
98
+ **Pairwise Encoder**
99
+ - Encode local neighborhood relationships in nD spatial volumes
100
+ - Multi-channel/class support
101
+ - Configurable neighborhood radius and sparsity patterns
102
+ - Outputs sparse unbatched/batched COO or CSR matrices for downstream processing
103
+ - Optimised for medical imaging and volumetric data applications
104
+
105
+ ### Graph Neural Network Operations
106
+
107
+ **Indexed Matrix Multiplication**
108
+ - `segment_mm`: Segmented matrix multiplication compatible with DGL/PyG
109
+ - `gather_mm`: Gather-based matrix multiplication for graph operations
110
+ - Pure PyTorch implementations as alternatives to [`dgl.ops.segment_mm`](https://docs.dgl.ai/generated/dgl.ops.segment_mm.html), [`pyg_lib.ops.segment_matmul`](https://pyg-lib.readthedocs.io/en/latest/modules/ops.html#pyg_lib.ops.segment_matmul), and [`dgl.ops.gather_mm`](https://docs.dgl.ai/generated/dgl.ops.gather_mm.html)
111
+ - Supports PyTorch >= 2.4 with nested tensor operations
112
+
113
+
114
+
115
+ ## 🛠️ Installation
116
+
117
+ ### Basic Installation
118
+
119
+ The package can be installed using pip:
120
+
121
+ ```bash
122
+ pip install torchsparsegradutils
123
+ ```
124
+
125
+ ### Development Installation
126
+
127
+ For the latest features and development work:
128
+
129
+ ```bash
130
+ pip install git+https://github.com/cai4cai/torchsparsegradutils
131
+ ```
132
+
133
+ ### Optional Dependencies
134
+
135
+ For full functionality, install optional dependencies:
136
+
137
+ ```bash
138
+ # For CuPy sparse solver support (GPU acceleration)
139
+ pip install cupy-cuda12x # Replace with your CUDA version
140
+
141
+ # For JAX sparse solver support
142
+ pip install "jax[cpu]" # CPU version
143
+ pip install "jax[cuda12]" # GPU version (replace with your CUDA version)
144
+
145
+ # For benchmarking and testing
146
+ pip install scipy matplotlib pandas tqdm pytest
147
+ ```
148
+
149
+ ### Requirements
150
+
151
+ - **Python**: ≥ 3.10
152
+ - **PyTorch**: ≥ 2.5 (≥ 2.4 for indexed operations)
153
+ - **Operating Systems**: Linux, macOS, Windows
154
+ - **Hardware**: CPU and CUDA GPU support
155
+
156
+
157
+ ## 📊 Performance Benchmarks
158
+
159
+ Our comprehensive benchmark suite demonstrates significant performance improvements across various sparse operations. All benchmarks were conducted on an NVIDIA GeForce RTX 4090 with PyTorch 2.8.0+cu128. Benchmarks are performed using [Rothberg/cfd2](https://suitesparse-collection-website.herokuapp.com/Rothberg/cfd2) matrix from [SuiteSparse Matrix Collection](https://suitesparse-collection-website.herokuapp.com/)
160
+
161
+ ![Sparse MM Suite Performance (int32/float32 COO)](torchsparsegradutils/benchmarks/benchmark_visualizations/sparse_mm_suite_performance_int32_float32_coo.png)
162
+
163
+ ![Sparse Triangular Solve Suite Performance (int32/float32 COO)](torchsparsegradutils/benchmarks/benchmark_visualizations/triangular_solve_suitesparse_performance_int32_float32_coo.png)
164
+
165
+ ![Sparse Genertic Solve Suite Performance (int32/float32 COO)](torchsparsegradutils/benchmarks/benchmark_visualizations/sparse_solve_suite_performance_int32_float32_coo.png)
166
+
167
+ ## 🚀 Quick Start
168
+
169
+ ### Basic Sparse Matrix Multiplication
170
+
171
+ ```python
172
+ import torch
173
+ from torchsparsegradutils import sparse_mm
174
+
175
+ # Create sparse matrix in COO format
176
+ indices = torch.tensor([[0, 1, 1], [2, 0, 2]], dtype=torch.int64)
177
+ values = torch.tensor([3., 4., 5.], requires_grad=True)
178
+ A = torch.sparse_coo_tensor(indices, values, (2, 3))
179
+
180
+ # Dense matrix
181
+ B = torch.randn(3, 4, requires_grad=True)
182
+
183
+ # Memory-efficient sparse matrix multiplication with gradient support
184
+ C = sparse_mm(A, B)
185
+ loss = C.sum()
186
+ loss.backward() # Gradients preserved in sparse format
187
+
188
+ print(f"A.grad: {A.grad}") # Sparse gradient
189
+ print(f"B.grad: {B.grad}") # Dense gradient
190
+ ```
191
+
192
+ ### Sparse Linear System Solving
193
+
194
+ ```python
195
+ import torch
196
+ from torchsparsegradutils import sparse_triangular_solve, sparse_generic_solve
197
+ from torchsparsegradutils.utils import linear_cg
198
+
199
+ # Create sparse triangular matrix
200
+ A = create_sparse_triangular_matrix() # Your sparse CSR matrix
201
+ b = torch.randn(A.shape[0], requires_grad=True)
202
+
203
+ # Triangular solve (fast for triangular systems)
204
+ x1 = sparse_triangular_solve(A, b, upper=False)
205
+
206
+ # Generic solve with different backends
207
+ x2 = sparse_generic_solve(A, b, solve=linear_cg, tol=1e-6)
208
+
209
+ # Using CuPy backend (if available)
210
+ from torchsparsegradutils.cupy import sparse_solve_c4t
211
+ x3 = sparse_solve_c4t(A, b, solve="cg", tol=1e-6)
212
+ ```
213
+
214
+ ### Sparse Multivariate Normal Distribution
215
+
216
+ ```python
217
+ import torch
218
+ from torchsparsegradutils.distributions import SparseMultivariateNormal
219
+ from torchsparsegradutils.utils.random_sparse import rand_sparse_tri
220
+
221
+ # Create parameters
222
+ batch_size, event_size = 2, 1000
223
+ loc = torch.zeros(batch_size, event_size)
224
+
225
+ # Example 1: LDL^T parameterization (numerically stable for precision matrices)
226
+ # Create sparse lower triangular matrix (unit triangular, no diagonal)
227
+ scale_tril = rand_sparse_tri(
228
+ (batch_size, event_size, event_size),
229
+ nnz=5000, # 5000 non-zeros for 1M parameters (0.5% sparsity)
230
+ layout=torch.sparse_csr,
231
+ upper=False,
232
+ unit_triangular=True # Unit triangular for LDL^T
233
+ )
234
+
235
+ # Diagonal component for LDL^T parameterization
236
+ diagonal = torch.ones(batch_size, event_size) * 0.5
237
+
238
+ # Create distribution with LDL^T parameterization
239
+ dist_ldlt = SparseMultivariateNormal(
240
+ loc=loc,
241
+ diagonal=diagonal,
242
+ scale_tril=scale_tril # Unit lower triangular
243
+ )
244
+
245
+ # Example 2: LL^T parameterization (standard Cholesky)
246
+ scale_tril_chol = rand_sparse_tri(
247
+ (batch_size, event_size, event_size),
248
+ nnz=5000,
249
+ layout=torch.sparse_csr,
250
+ upper=False,
251
+ unit_triangular=False # Include diagonal for LL^T
252
+ )
253
+
254
+ # Create distribution with LL^T parameterization
255
+ dist_chol = SparseMultivariateNormal(
256
+ loc=loc,
257
+ scale_tril=scale_tril_chol # Lower triangular with diagonal
258
+ )
259
+
260
+ # Example 3: Precision matrix parameterization (more stable with LDL^T)
261
+ precision_tril = rand_sparse_tri(
262
+ (batch_size, event_size, event_size),
263
+ nnz=5000,
264
+ layout=torch.sparse_csr,
265
+ upper=False,
266
+ unit_triangular=True
267
+ )
268
+
269
+ precision_diagonal = torch.ones(batch_size, event_size) * 2.0
270
+
271
+ dist_precision = SparseMultivariateNormal(
272
+ loc=loc,
273
+ diagonal=precision_diagonal,
274
+ precision_tril=precision_tril # Unit triangular precision factor
275
+ )
276
+
277
+ # Sample with gradient support
278
+ samples = dist_ldlt.rsample((100,)) # 100 samples
279
+
280
+ # Gradient computation preserves sparsity
281
+ loss = samples.sum()
282
+ loss.backward()
283
+ print(f"Sparse gradient shape: {scale_tril.grad.shape}")
284
+ print(f"Sparse gradient nnz: {scale_tril.grad._nnz()}")
285
+ print(f"Using LDL^T parameterization: {dist_ldlt.is_ldlt_parameterization}")
286
+ ```
287
+
288
+ ### Pairwise Voxel Encoding
289
+
290
+ ```python
291
+ import torch
292
+ from torchsparsegradutils.encoders import PairwiseEncoder
293
+
294
+ # Create 3D volume encoder (channels, height, depth, width)
295
+ volume_shape = (4, 64, 64, 64) # 4 channels, 64x64x64 spatial
296
+ encoder = PairwiseEncoder(
297
+ radius=2.0,
298
+ volume_shape=volume_shape,
299
+ layout=torch.sparse_csr
300
+ )
301
+
302
+ # Generate values for each spatial relationship offset
303
+ num_offsets = len(encoder.offsets)
304
+ values = torch.randn(num_offsets, *volume_shape)
305
+
306
+ # Generate sparse encoding matrix
307
+ sparse_matrix = encoder(values)
308
+
309
+ print(f"Encoded volume shape: {sparse_matrix.shape}")
310
+ print(f"Sparsity: {sparse_matrix._nnz() / sparse_matrix.numel():.3%}")
311
+ print(f"Number of spatial offsets: {num_offsets}")
312
+
313
+ # Use in sparse multivariate normal
314
+ flat_size = 4 * 64 * 64 * 64 # Total flattened size
315
+ dist = SparseMultivariateNormal(
316
+ loc=torch.zeros(flat_size),
317
+ scale_tril=sparse_matrix
318
+ )
319
+ ```
320
+
321
+ #### Spatial Relationship Visualization
322
+
323
+ The encoder creates sparse matrices that encode pairwise spatial relationships within a specified radius. Different channel relationship types affect how channels interact:
324
+
325
+ - **`indep`**: Independent channels (only spatial neighbors within same channel)
326
+ - **`intra`**: Intra-channel relationships (spatial neighbors within same channel)
327
+ - **`inter`**: Inter-channel relationships (spatial neighbors across all channels)
328
+
329
+ **3D Spatial Grid (3×3×3×3) with Different Channel Relations:**
330
+
331
+ <div align="center">
332
+
333
+ **Radius = 1.0**
334
+ ![Spatial Encodings Radius 1](torchsparsegradutils/tests/test_outputs/sparse_encodings_radius_1.png)
335
+
336
+ **Radius = 2.0**
337
+ ![Spatial Encodings Radius 2](torchsparsegradutils/tests/test_outputs/sparse_encodings_radius_2.png)
338
+ <!--
339
+ **Legend for Spatial Offsets:**
340
+ <table>
341
+ <tr>
342
+ <td><img src="torchsparsegradutils/tests/test_outputs/legend_radius_1.png" width="150"/></td>
343
+ <td><img src="torchsparsegradutils/tests/test_outputs/legend_radius_2.png" width="150"/></td>
344
+ </tr>
345
+ <tr>
346
+ <td align="center">Radius 1.0 Offsets</td>
347
+ <td align="center">Radius 2.0 Offsets</td>
348
+ </tr>
349
+ </table> -->
350
+
351
+ </div>
352
+
353
+ Each color represents a different spatial offset (relative position) in the 3D neighborhood. The sparse matrix encodes these relationships efficiently, enabling:
354
+
355
+ - **Local spatial modeling** for volumetric data (medical imaging, 3D computer vision)
356
+ - **Multi-channel feature interaction** in convolutional architectures
357
+ - **Sparse graph construction** from regular grids
358
+ - **Memory-efficient neighborhood encoding** for large volumes
359
+
360
+ **Key Parameters:**
361
+ - `radius`: Spatial neighborhood radius (1.0 = immediate neighbors, 2.0 = extended neighborhood)
362
+ - `volume_shape`: `(channels, height, depth, width)` for 4D volumes
363
+ - `channel_voxel_relation`: Controls cross-channel connectivity patterns
364
+ - `layout`: Output sparse format (`torch.sparse_coo` or `torch.sparse_csr`)
365
+
366
+ ### Indexed Matrix Operations (Graph Neural Networks)
367
+
368
+ ```python
369
+ import torch
370
+ from torchsparsegradutils import segment_mm, gather_mm
371
+
372
+ # Segment matrix multiplication (compatible with DGL/PyG)
373
+ a = torch.randn(15, 10, requires_grad=True) # Node features
374
+ b = torch.randn(3, 10, 5, requires_grad=True) # Edge type embeddings
375
+ seglen_a = torch.tensor([5, 6, 4]) # Segment lengths
376
+
377
+ # Performs: a[0:5] @ b[0], a[5:11] @ b[1], a[11:15] @ b[2]
378
+ result = segment_mm(a, b, seglen_a)
379
+
380
+ # Gather matrix multiplication
381
+ indices = torch.tensor([0, 0, 1, 1, 2])
382
+ a_gathered = torch.randn(5, 10, requires_grad=True)
383
+ result = gather_mm(a_gathered, b, indices)
384
+ ```
385
+
386
+ ### Statistical Distribution Validation
387
+
388
+ ```python
389
+ import torch
390
+ from torch.distributions import MultivariateNormal
391
+ from torchsparsegradutils.utils import mean_hotelling_t2_test, cov_nagao_test
392
+
393
+ # Generate sample data from known distribution
394
+ torch.manual_seed(42)
395
+ true_mean = torch.tensor([[0.0, 0.0]])
396
+ true_cov = torch.eye(2).unsqueeze(0)
397
+ n = 1000
398
+
399
+ # Generate samples and compute statistics
400
+ dist = MultivariateNormal(true_mean.squeeze(0), true_cov.squeeze(0))
401
+ samples = dist.sample((n,)).unsqueeze(1)
402
+ sample_mean = samples.mean(0)
403
+ sample_cov = torch.cov(samples.squeeze(1).T).unsqueeze(0)
404
+
405
+ # Test if sample mean is consistent with hypothesized mean (should pass)
406
+ result, t2_stat, threshold = mean_hotelling_t2_test(
407
+ sample_mean, true_mean, sample_cov, n, confidence_level=0.95
408
+ )
409
+ print(f"Mean test passed: {result.item()}") # True
410
+
411
+ # Test if sample covariance is consistent with hypothesized covariance (should pass)
412
+ result, t_n_stat, threshold = cov_nagao_test(
413
+ sample_cov, true_cov, n, confidence_level=0.95
414
+ )
415
+ print(f"Covariance test passed: {result.item()}") # True
416
+
417
+ # Test against wrong parameters (should fail)
418
+ wrong_mean = true_mean + 1.0 # Significantly different mean
419
+ result, _, _ = mean_hotelling_t2_test(
420
+ sample_mean, wrong_mean, sample_cov, n, confidence_level=0.95
421
+ )
422
+ print(f"Wrong mean test passed: {result.item()}") # False
423
+ ```
424
+
425
+ ## 🧪 Testing and Benchmarks
426
+
427
+ ### Running Tests
428
+
429
+ ```bash
430
+ # Run all tests
431
+ python -m pytest
432
+
433
+ # Run specific test modules
434
+ python -m pytest torchsparsegradutils/tests/test_sparse_matmul.py
435
+ python -m pytest torchsparsegradutils/tests/test_distributions.py
436
+
437
+ # Run with coverage
438
+ python -m pytest --cov=torchsparsegradutils
439
+ ```
440
+
441
+ ### Running Benchmarks
442
+
443
+ The package includes comprehensive benchmarks for performance evaluation:
444
+
445
+ ```bash
446
+ # Sparse matrix multiplication benchmarks
447
+ python -m torchsparsegradutils.benchmarks.sparse_mm_rand
448
+ python -m torchsparsegradutils.benchmarks.batched_sparse_mm_rand
449
+
450
+ # Triangular solver benchmarks
451
+ python -m torchsparsegradutils.benchmarks.sparse_triangular_solve_rand
452
+
453
+ # Generic solver benchmarks
454
+ python -m torchsparsegradutils.benchmarks.sparse_generic_solve_suite
455
+
456
+ # SuiteSparse matrix benchmarks
457
+ python -m torchsparsegradutils.benchmarks.sparse_mm_suite
458
+ ```
459
+
460
+ Results are automatically saved to `torchsparsegradutils/benchmarks/results/` as CSV files.
461
+
462
+ ### Utility Functions
463
+
464
+ #### `torchsparsegradutils.utils.random_sparse`
465
+
466
+ **Sparse Random Matrix Generators**
467
+ - **`rand_sparse(size, nnz, layout=torch.sparse_coo, **kwargs)`**: Generate random sparse matrices with specified layout and properties
468
+ - Supports COO and CSR
469
+ - Supports batch dimension
470
+ - **`rand_sparse_tri(size, nnz, layout=torch.sparse_coo, upper=True, strict=False, **kwargs)`**: Generate random sparse triangular matrices
471
+ - Supports COO and CSR
472
+ - Supports batch dimension
473
+ - Strict triangular (no diagonal) or non-strict (with diagonal values)
474
+ - Option to produce well conditioned matrices and regulate diagonal values
475
+
476
+ - **`make_spd_sparse(n, layout, value_dtype, index_dtype, device, sparsity_ratio=0.5, nz=None)`**: Generate sparse symmetric positive definite (SPD) matrices
477
+
478
+ #### `torchsparsegradutils.utils.utils`
479
+
480
+ **Sparse Matrix Operations**
481
+ - **`sparse_block_diag(*sparse_tensors)`**: Create block diagonal sparse matrix from multiple sparse tensors
482
+ - **`sparse_block_diag_split(sparse_block_diag_tensor, *shapes)`**: Split block diagonal sparse matrix into original sparse tensors
483
+ - **`sparse_eye(size, layout=torch.sparse_coo, **kwargs)`**: Create batched or unbatched sparse identity matrices
484
+ - **`stack_csr(tensors, dim=0)`**: Stack CSR tensors along batch dimension (like torch.stack for CSR)
485
+
486
+ **Sparse Format Conversion**
487
+ - **`convert_coo_to_csr_indices_values(coo_indices, num_rows, values=None)`**: Convert COO indices and values to CSR format, with support for batch dimension
488
+ - **`convert_coo_to_csr(sparse_coo_tensor)`**: Convert COO sparse tensor to CSR format with batch support
489
+
490
+ #### `torchsparsegradutils.utils.dist_stats_helpers`
491
+
492
+ **Statistical Distribution Validation**
493
+ - **`mean_hotelling_t2_test(sample_mean, true_mean, sample_cov, n, confidence_level=0.95)`**: One-sample Hotelling T² test for multivariate mean equality using confidence regions
494
+ - Tests whether hypothesized mean vector lies within confidence region around sample mean
495
+ - Uses F-distribution for threshold calculation with proper degrees of freedom
496
+ - Higher confidence levels create larger (more permissive) acceptance regions
497
+ - **`cov_nagao_test(emp_cov, ref_cov, n, confidence_level=0.95)`**: Nagao's test for covariance matrix equality using confidence regions
498
+ - Tests whether hypothesized covariance matrix is consistent with empirical covariance
499
+ - Uses χ² distribution with appropriate degrees of freedom
500
+ - Standardizes covariance matrices for improved numerical stability
501
+
502
+
503
+ ## 🤝 Contributing
504
+
505
+ We welcome contributions! Please see our contributing guidelines:
506
+
507
+ 1. **Issues**: Report bugs and request features via [GitHub Issues](https://github.com/cai4cai/torchsparsegradutils/issues)
508
+ 2. **Pull Requests**: Submit improvements via GitHub PRs
509
+ 3. **Testing**: Ensure all tests pass and add tests for new functionality
510
+ 4. **Documentation**: Update docstrings and examples for new features
511
+ 5. **Benchmarks**: Include performance benchmarks for new operations
512
+
513
+ ### Development Setup
514
+
515
+ #### Option 1: Local Development
516
+
517
+ ```bash
518
+ git clone https://github.com/cai4cai/torchsparsegradutils
519
+ cd torchsparsegradutils
520
+ pip install -e ".[dev]" # Install in development mode
521
+ pre-commit install # Install pre-commit hooks
522
+ ```
523
+
524
+ #### Option 2: Development Containers (Recommended)
525
+
526
+ For a consistent development environment with GPU support and all dependencies pre-installed, use VS Code Dev Containers:
527
+
528
+ **Prerequisites:**
529
+ - [Docker](https://docs.docker.com/get-docker/) with NVIDIA Container Toolkit (for GPU support)
530
+ - [VS Code](https://code.visualstudio.com/) with the [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers)
531
+
532
+ **Quick Start:**
533
+ 1. Clone the repository and open in VS Code:
534
+ ```bash
535
+ git clone https://github.com/cai4cai/torchsparsegradutils
536
+ cd torchsparsegradutils
537
+ code .
538
+ ```
539
+
540
+ 2. When prompted, click **"Reopen in Container"** or use the Command Palette:
541
+ - Press `Ctrl+Shift+P` (or `Cmd+Shift+P` on macOS)
542
+ - Type "Dev Containers: Reopen in Container"
543
+
544
+ **Available Configurations:**
545
+
546
+ - **`.devcontainer/Dockerfile.stable`** (default): Uses stable PyTorch with CUDA 12.8 support
547
+ - **`.devcontainer/Dockerfile.nightly`**: Uses nightly PyTorch builds for latest features
548
+
549
+ To switch configurations, modify the `dockerfile` field in `.devcontainer/devcontainer.json`:
550
+ ```json
551
+ "build": {
552
+ "dockerfile": "./Dockerfile.nightly", // or "./Dockerfile.stable"
553
+ "context": "."
554
+ }
555
+ ```
556
+
557
+ **What's Included:**
558
+ - **CUDA 12.8**: Full GPU development support with NVIDIA drivers
559
+ - **Pre-installed Dependencies**: PyTorch, CuPy, JAX, SciPy, and all development tools
560
+ - **VS Code Extensions**: Python, Pylance, Jupyter, GitHub Copilot, and code formatting tools
561
+ - **Development Tools**: pytest, black, flake8, pre-commit hooks
562
+ - **Python Environment**: Python 3.10+ with all optional dependencies
563
+
564
+ **Benefits:**
565
+ - ✅ **Consistent Environment**: Same setup across different machines
566
+ - ✅ **GPU Support**: Pre-configured CUDA environment
567
+ - ✅ **Zero Setup**: All dependencies and tools pre-installed
568
+ - ✅ **Isolated**: No conflicts with host system packages
569
+ - ✅ **VS Code Integration**: Seamless debugging, IntelliSense, and testing
570
+
571
+ ## 📄 License
572
+
573
+ This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.
574
+
575
+ ## 🙏 Acknowledgments
576
+
577
+ - **PyTorch Team**: For the foundational sparse tensor implementations
578
+ - **SciPy/CuPy Teams**: For high-performance sparse linear algebra routines
579
+ - **JAX Team**: For cross-platform sparse operations and XLA compilation
580
+ - **Open Source Libraries**: We port and adapt algorithms from:
581
+ - [pykrylov](https://github.com/PythonOptimizers/pykrylov) (BICGSTAB)
582
+ - [cornellius-gp/linear_operator](https://github.com/cornellius-gp/linear_operator) (CG, MINRES)
583
+ - [pytorch-minimize](https://github.com/rfeinman/pytorch-minimize) (LSMR)
584
+
585
+ ## 📚 Citation
586
+
587
+ If you use this package in your research, please cite:
588
+
589
+ ```bibtex
590
+ @software{torchsparsegradutils,
591
+ title={torchsparsegradutils: Sparsity-preserving gradient utility tools for PyTorch},
592
+ author={Barfoot, Theodore and Glocker, Ben and Vercauteren, Tom},
593
+ url={https://github.com/cai4cai/torchsparsegradutils},
594
+ year={2024}
595
+ }
596
+ ```
597
+
598
+ ## ⚠️ Known Issues
599
+
600
+ ### PyTorch Sparse COO Index Dtype Conversion
601
+
602
+ **Issue**: PyTorch automatically converts `int32` indices to `int64` when creating sparse COO tensors, but preserves `int32` for sparse CSR tensors. This affects memory usage and performance for algorithms that benefit from `int32` indices (such as `sparse_mm`).
603
+
604
+ **Impact**:
605
+ - **Memory**: `int64` indices use 2× more memory than `int32`
606
+ - **Performance**: Some sparse operations may run faster with `int32` indices
607
+ - **Cross-format consistency**: Different behavior between COO and CSR formats
608
+
609
+ **Example**:
610
+ ```python
611
+ import torch
612
+
613
+ # Demonstrate the issue
614
+ indices_int32 = torch.tensor([[0, 1], [1, 0]], dtype=torch.int32)
615
+ values = torch.tensor([1.0, 2.0])
616
+
617
+ print(f"Original indices dtype: {indices_int32.dtype}") # torch.int32
618
+
619
+ # COO: int32 -> int64 conversion happens
620
+ coo_tensor = torch.sparse_coo_tensor(indices_int32, values, (2, 2)).coalesce()
621
+ print(f"COO indices dtype: {coo_tensor.indices().dtype}") # torch.int64 (converted!)
622
+
623
+ # CSR: int32 is preserved
624
+ crow_indices = torch.tensor([0, 1, 2], dtype=torch.int32)
625
+ col_indices = torch.tensor([1, 0], dtype=torch.int32)
626
+ csr_tensor = torch.sparse_csr_tensor(crow_indices, col_indices, values, (2, 2))
627
+ print(f"CSR crow_indices dtype: {csr_tensor.crow_indices().dtype}") # torch.int32 (preserved!)
628
+ print(f"CSR col_indices dtype: {csr_tensor.col_indices().dtype}") # torch.int32 (preserved!)
629
+ ```
630
+
631
+ **Workarounds**:
632
+ 1. **Use CSR format** when `int32` indices are important for performance
633
+ 2. **Account for extra memory** when using COO format with large sparse matrices
634
+ 3. **Test performance** with both dtypes to determine if the conversion impacts your use case
635
+
636
+ **Status**: This is a known PyTorch behavior. Our test suite documents and validates this behavior to catch any future changes in PyTorch's handling of sparse tensor index dtypes.
637
+
638
+ ### PairwiseEncoder CSR Memory Usage Issue
639
+
640
+ **Issue**: CSR sparse tensors generated by `PairwiseEncoder` consume significantly more memory during backward passes compared to COO format, particularly in integration tests with `SparseMultivariateNormal`.
641
+
642
+ **Impact**:
643
+ - **Memory Consumption**: CSR integration tests can use 2-3x more memory than equivalent COO tests during `.backward()`
644
+ - **Training Stability**: May cause out-of-memory errors during training with large spatial volumes
645
+ - **Development**: Affects integration testing with large tensor configurations
646
+
647
+ **Suspected Cause**: The issue may be related to CSR permutation operations within `PairwiseEncoder` that create additional intermediate tensors during gradient computation.
648
+
649
+ **Current Status**: Under investigation. The memory spike occurs specifically during backpropagation through the sparse matrix operations.
650
+
651
+ **Workarounds**:
652
+ 1. **Use COO format** for `PairwiseEncoder` when memory is constrained during training
653
+ 2. **Reduce batch sizes** or spatial dimensions when using CSR format
654
+ 3. **Monitor memory usage** carefully when integrating `PairwiseEncoder` with gradient-based optimization
655
+
656
+ **Example**:
657
+ ```python
658
+ # More memory-efficient approach for large tensors
659
+ encoder = PairwiseEncoder(
660
+ radius=2.0,
661
+ volume_shape=(4, 64, 64, 64),
662
+ layout=torch.sparse_coo # Use COO instead of CSR for memory efficiency
663
+ )
664
+ ```
665
+
666
+ ### SparseMultivariateNormal LL^T Precision Parameterization Gradient Issues
667
+
668
+ **Issue**: Large gradient magnitudes can occur when using LL^T parameterization with precision matrices in `SparseMultivariateNormal`, leading to training instability.
669
+
670
+ **Impact**:
671
+ - **Gradient Explosion**: Gradients can become extremely large (>1e6) during backpropagation
672
+ - **Training Instability**: May cause NaN values or divergent optimization
673
+ - **Numerical Issues**: Poor conditioning of the precision matrix can amplify gradient problems
674
+
675
+ **Affected Configurations**:
676
+ - LL^T parameterization (`scale_tril` parameter) combined with precision matrix formulation
677
+ - Both 2D and 3D spatial configurations show this behavior
678
+ - More pronounced with larger spatial dimensions and higher sparsity
679
+
680
+ **Root Cause**: The LL^T precision parameterization can lead to poor numerical conditioning, especially when the triangular matrix has small diagonal values or high condition number.
681
+
682
+ **Recommended Solution**: Use LDL^T parameterization instead, which provides better numerical stability:
683
+
684
+ ```python
685
+ # Problematic: LL^T precision parameterization
686
+ dist_unstable = SparseMultivariateNormal(
687
+ loc=loc,
688
+ precision_tril=scale_tril # LL^T with precision - can cause large gradients
689
+ )
690
+
691
+ # Better: LDL^T parameterization with separate diagonal
692
+ dist_stable = SparseMultivariateNormal(
693
+ loc=loc,
694
+ diagonal=diagonal, # Separate diagonal component for stability
695
+ precision_tril=unit_triangular_matrix # Unit triangular (LDL^T)
696
+ )
697
+ ```
698
+
699
+ **Benefits of LDL^T Parameterization**:
700
+ - **Numerical Stability**: Separates diagonal scaling from triangular structure
701
+ - **Gradient Stability**: More stable gradients during backpropagation
702
+ - **No SPD Constraints**: Doesn't require strict positive definiteness
703
+ - **Better Conditioning**: Diagonal component can be controlled independently
704
+
705
+ **Status**: This is a known limitation of the LL^T precision formulation. LDL^T parameterization is the recommended approach for precision matrices.