torchsparsegradutils 0.2.4__tar.gz → 0.2.6__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.
- {torchsparsegradutils-0.2.4/torchsparsegradutils.egg-info → torchsparsegradutils-0.2.6}/PKG-INFO +11 -8
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/README.md +10 -7
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/docs/source/conf.py +2 -2
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/pyproject.toml +7 -1
- torchsparsegradutils-0.2.6/torchsparsegradutils/_compat.py +48 -0
- torchsparsegradutils-0.2.6/torchsparsegradutils/benchmarks/linear_cg_convergence.py +224 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/benchmarks/sparse_generic_solve_rand.py +2 -2
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/benchmarks/sparse_generic_solve_suite.py +3 -4
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/benchmarks/sparse_triangular_solve_rand.py +16 -12
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/benchmarks/sparse_triangular_solve_suitesparse.py +15 -12
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/sparse_solve.py +8 -10
- torchsparsegradutils-0.2.6/torchsparsegradutils/tests/test_deprecated_torch_apis.py +62 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/tests/test_integration_pairwise_sparse_mvn.py +7 -7
- torchsparsegradutils-0.2.6/torchsparsegradutils/tests/test_linear_cg.py +366 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/tests/test_minres.py +1 -1
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/tests/test_quickstart_guide.py +2 -7
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/tests/test_random.py +1 -1
- torchsparsegradutils-0.2.6/torchsparsegradutils/tests/test_release_version.py +94 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/tests/test_sparse_triangular_solve.py +104 -79
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/utils/__init__.py +2 -1
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/utils/linear_cg.py +228 -67
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/utils/lsmr.py +6 -6
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/utils/minres.py +3 -3
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6/torchsparsegradutils.egg-info}/PKG-INFO +11 -8
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils.egg-info/SOURCES.txt +4 -0
- torchsparsegradutils-0.2.4/torchsparsegradutils/tests/test_linear_cg.py +0 -104
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/LICENSE +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/MANIFEST.in +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/setup.cfg +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/setup.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/__init__.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/benchmarks/__init__.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/benchmarks/batched_sparse_mm_rand.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/benchmarks/benchmark_suite.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/benchmarks/benchmark_utils.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/benchmarks/sparse_bidir_logsumexp_rand.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/benchmarks/sparse_bidir_logsumexp_suitesparse.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/benchmarks/sparse_logsumexp_rand.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/benchmarks/sparse_logsumexp_suitesparse.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/benchmarks/sparse_mm_rand.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/benchmarks/sparse_mm_suite.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/benchmarks/visualize_benchmark_results.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/cupy/__init__.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/cupy/cupy_bindings.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/cupy/cupy_sparse_solve.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/distributions/__init__.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/distributions/constraints.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/distributions/sparse_multivariate_normal.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/encoders/__init__.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/encoders/pairwise_encoder.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/encoders/pairwise_voxel_encoder.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/indexed_matmul.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/jax/__init__.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/jax/jax_bindings.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/jax/jax_sparse_solve.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/sparse_logsumexp.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/sparse_lstsq.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/sparse_matmul.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/tests/__init__.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/tests/conftest.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/tests/test_bicgstab.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/tests/test_config.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/tests/test_cupy_bindings.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/tests/test_cupy_sparse_solve.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/tests/test_dist_stats_helpers.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/tests/test_distributions.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/tests/test_doctests.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/tests/test_encoders.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/tests/test_indexed_matmul.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/tests/test_jax_bindings.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/tests/test_jax_sparse_solve.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/tests/test_lsmr.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/tests/test_params/czyx_shifts.yaml +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/tests/test_params/pairwise_coo_indices.yaml +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/tests/test_params/xyz_coords.yaml +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/tests/test_sparse_bidir_logsumexp.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/tests/test_sparse_logsumexp.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/tests/test_sparse_lstsq.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/tests/test_sparse_matmul.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/tests/test_sparse_solve.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/tests/test_utils.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/utils/bicgstab.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/utils/dist_stats_helpers.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/utils/random_sparse.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/utils/utils.py +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils.egg-info/dependency_links.txt +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils.egg-info/not-zip-safe +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils.egg-info/requires.txt +0 -0
- {torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils.egg-info/top_level.txt +0 -0
{torchsparsegradutils-0.2.4/torchsparsegradutils.egg-info → torchsparsegradutils-0.2.6}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: torchsparsegradutils
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
4
4
|
Summary: A collection of utility functions to work with PyTorch sparse tensors
|
|
5
5
|
Author-email: CAI4CAI research group <contact@cai4cai.uk>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -239,15 +239,18 @@ batch_size, event_size = 2, 1000
|
|
|
239
239
|
loc = torch.zeros(batch_size, event_size)
|
|
240
240
|
|
|
241
241
|
# Example 1: LDL^T parameterization (numerically stable for precision matrices)
|
|
242
|
-
# Create sparse lower triangular matrix (
|
|
242
|
+
# Create sparse lower triangular matrix (strictly lower triangular, no diagonal)
|
|
243
|
+
|
|
243
244
|
scale_tril = rand_sparse_tri(
|
|
244
245
|
(batch_size, event_size, event_size),
|
|
245
|
-
nnz=5000, # 5000 non-zeros for
|
|
246
|
+
nnz=5000, # 5000 non-zeros for 1000x1000 dense matrix (99.5% sparsity)
|
|
246
247
|
layout=torch.sparse_csr,
|
|
247
248
|
upper=False,
|
|
248
|
-
|
|
249
|
+
strict=True # Strict triangular (exclude diagonal)
|
|
249
250
|
)
|
|
250
251
|
|
|
252
|
+
scale_tril.requires_grad_(True)
|
|
253
|
+
|
|
251
254
|
# Diagonal component for LDL^T parameterization
|
|
252
255
|
diagonal = torch.ones(batch_size, event_size) * 0.5
|
|
253
256
|
|
|
@@ -255,7 +258,7 @@ diagonal = torch.ones(batch_size, event_size) * 0.5
|
|
|
255
258
|
dist_ldlt = SparseMultivariateNormal(
|
|
256
259
|
loc=loc,
|
|
257
260
|
diagonal=diagonal,
|
|
258
|
-
scale_tril=scale_tril #
|
|
261
|
+
scale_tril=scale_tril # Strictly lower triangular
|
|
259
262
|
)
|
|
260
263
|
|
|
261
264
|
# Example 2: LL^T parameterization (standard Cholesky)
|
|
@@ -264,7 +267,7 @@ scale_tril_chol = rand_sparse_tri(
|
|
|
264
267
|
nnz=5000,
|
|
265
268
|
layout=torch.sparse_csr,
|
|
266
269
|
upper=False,
|
|
267
|
-
|
|
270
|
+
strict=False # Include diagonal
|
|
268
271
|
)
|
|
269
272
|
|
|
270
273
|
# Create distribution with LL^T parameterization
|
|
@@ -279,7 +282,7 @@ precision_tril = rand_sparse_tri(
|
|
|
279
282
|
nnz=5000,
|
|
280
283
|
layout=torch.sparse_csr,
|
|
281
284
|
upper=False,
|
|
282
|
-
|
|
285
|
+
strict=True
|
|
283
286
|
)
|
|
284
287
|
|
|
285
288
|
precision_diagonal = torch.ones(batch_size, event_size) * 2.0
|
|
@@ -287,7 +290,7 @@ precision_diagonal = torch.ones(batch_size, event_size) * 2.0
|
|
|
287
290
|
dist_precision = SparseMultivariateNormal(
|
|
288
291
|
loc=loc,
|
|
289
292
|
diagonal=precision_diagonal,
|
|
290
|
-
precision_tril=precision_tril #
|
|
293
|
+
precision_tril=precision_tril # Strictly triangular precision factor
|
|
291
294
|
)
|
|
292
295
|
|
|
293
296
|
# Sample with gradient support
|
|
@@ -192,15 +192,18 @@ batch_size, event_size = 2, 1000
|
|
|
192
192
|
loc = torch.zeros(batch_size, event_size)
|
|
193
193
|
|
|
194
194
|
# Example 1: LDL^T parameterization (numerically stable for precision matrices)
|
|
195
|
-
# Create sparse lower triangular matrix (
|
|
195
|
+
# Create sparse lower triangular matrix (strictly lower triangular, no diagonal)
|
|
196
|
+
|
|
196
197
|
scale_tril = rand_sparse_tri(
|
|
197
198
|
(batch_size, event_size, event_size),
|
|
198
|
-
nnz=5000, # 5000 non-zeros for
|
|
199
|
+
nnz=5000, # 5000 non-zeros for 1000x1000 dense matrix (99.5% sparsity)
|
|
199
200
|
layout=torch.sparse_csr,
|
|
200
201
|
upper=False,
|
|
201
|
-
|
|
202
|
+
strict=True # Strict triangular (exclude diagonal)
|
|
202
203
|
)
|
|
203
204
|
|
|
205
|
+
scale_tril.requires_grad_(True)
|
|
206
|
+
|
|
204
207
|
# Diagonal component for LDL^T parameterization
|
|
205
208
|
diagonal = torch.ones(batch_size, event_size) * 0.5
|
|
206
209
|
|
|
@@ -208,7 +211,7 @@ diagonal = torch.ones(batch_size, event_size) * 0.5
|
|
|
208
211
|
dist_ldlt = SparseMultivariateNormal(
|
|
209
212
|
loc=loc,
|
|
210
213
|
diagonal=diagonal,
|
|
211
|
-
scale_tril=scale_tril #
|
|
214
|
+
scale_tril=scale_tril # Strictly lower triangular
|
|
212
215
|
)
|
|
213
216
|
|
|
214
217
|
# Example 2: LL^T parameterization (standard Cholesky)
|
|
@@ -217,7 +220,7 @@ scale_tril_chol = rand_sparse_tri(
|
|
|
217
220
|
nnz=5000,
|
|
218
221
|
layout=torch.sparse_csr,
|
|
219
222
|
upper=False,
|
|
220
|
-
|
|
223
|
+
strict=False # Include diagonal
|
|
221
224
|
)
|
|
222
225
|
|
|
223
226
|
# Create distribution with LL^T parameterization
|
|
@@ -232,7 +235,7 @@ precision_tril = rand_sparse_tri(
|
|
|
232
235
|
nnz=5000,
|
|
233
236
|
layout=torch.sparse_csr,
|
|
234
237
|
upper=False,
|
|
235
|
-
|
|
238
|
+
strict=True
|
|
236
239
|
)
|
|
237
240
|
|
|
238
241
|
precision_diagonal = torch.ones(batch_size, event_size) * 2.0
|
|
@@ -240,7 +243,7 @@ precision_diagonal = torch.ones(batch_size, event_size) * 2.0
|
|
|
240
243
|
dist_precision = SparseMultivariateNormal(
|
|
241
244
|
loc=loc,
|
|
242
245
|
diagonal=precision_diagonal,
|
|
243
|
-
precision_tril=precision_tril #
|
|
246
|
+
precision_tril=precision_tril # Strictly triangular precision factor
|
|
244
247
|
)
|
|
245
248
|
|
|
246
249
|
# Sample with gradient support
|
|
@@ -15,8 +15,8 @@ sys.path.insert(0, os.path.abspath("../../"))
|
|
|
15
15
|
project = "torchsparsegradutils"
|
|
16
16
|
copyright = "2026, CAI4CAI research group"
|
|
17
17
|
author = "CAI4CAI research group"
|
|
18
|
-
release = "0.2.
|
|
19
|
-
version = "0.2.
|
|
18
|
+
release = "0.2.6"
|
|
19
|
+
version = "0.2.6"
|
|
20
20
|
|
|
21
21
|
# -- General configuration ---------------------------------------------------
|
|
22
22
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "torchsparsegradutils"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.6"
|
|
8
8
|
description = "A collection of utility functions to work with PyTorch sparse tensors"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -105,6 +105,12 @@ testpaths = [
|
|
|
105
105
|
pythonpath = [
|
|
106
106
|
"torchsparsegradutils/tests",
|
|
107
107
|
]
|
|
108
|
+
# Fail tests when deprecated upstream APIs are exercised outside intentional compatibility paths.
|
|
109
|
+
filterwarnings = [
|
|
110
|
+
'error:torch\.norm is deprecated.*:UserWarning',
|
|
111
|
+
'error:torch\.triangular_solve is deprecated.*:UserWarning',
|
|
112
|
+
'error:.*torch\.jit\.script.*is deprecated.*:DeprecationWarning',
|
|
113
|
+
]
|
|
108
114
|
doctest_optionflags = [
|
|
109
115
|
"NORMALIZE_WHITESPACE",
|
|
110
116
|
"IGNORE_EXCEPTION_DETAIL",
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Compatibility helpers for PyTorch APIs without feature-equivalent replacements."""
|
|
2
|
+
|
|
3
|
+
import warnings
|
|
4
|
+
|
|
5
|
+
import torch
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def linalg_solve_triangular_compat(
|
|
9
|
+
A: torch.Tensor,
|
|
10
|
+
B: torch.Tensor,
|
|
11
|
+
*,
|
|
12
|
+
upper: bool,
|
|
13
|
+
unitriangular: bool = False,
|
|
14
|
+
transpose: bool = False,
|
|
15
|
+
) -> torch.Tensor:
|
|
16
|
+
"""Solve a triangular system with the appropriate dense or sparse backend.
|
|
17
|
+
|
|
18
|
+
Dense coefficient matrices use ``torch.linalg.solve_triangular``. Sparse
|
|
19
|
+
matrices continue to use the isolated legacy call because the replacement
|
|
20
|
+
API does not support the sparse layouts required by this package. See
|
|
21
|
+
https://github.com/pytorch/pytorch/issues/87358 for upstream sparse feature
|
|
22
|
+
parity tracking.
|
|
23
|
+
"""
|
|
24
|
+
if A.layout == torch.strided:
|
|
25
|
+
if transpose:
|
|
26
|
+
A = A.transpose(-2, -1)
|
|
27
|
+
upper = not upper
|
|
28
|
+
|
|
29
|
+
return torch.linalg.solve_triangular(
|
|
30
|
+
A,
|
|
31
|
+
B,
|
|
32
|
+
upper=upper,
|
|
33
|
+
unitriangular=unitriangular,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
with warnings.catch_warnings():
|
|
37
|
+
warnings.filterwarnings(
|
|
38
|
+
"ignore",
|
|
39
|
+
message=r"torch\.triangular_solve is deprecated.*",
|
|
40
|
+
category=UserWarning,
|
|
41
|
+
)
|
|
42
|
+
return torch.triangular_solve(
|
|
43
|
+
B,
|
|
44
|
+
A,
|
|
45
|
+
upper=upper,
|
|
46
|
+
transpose=transpose,
|
|
47
|
+
unitriangular=unitriangular,
|
|
48
|
+
).solution
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Compare linear-CG convergence before and after the diagnostics fix.
|
|
3
|
+
|
|
4
|
+
This benchmark focuses on numerical behavior rather than throughput. It loads
|
|
5
|
+
the historical implementation from Git so both solvers run in one Python
|
|
6
|
+
environment on identical deterministic inputs.
|
|
7
|
+
|
|
8
|
+
Example
|
|
9
|
+
-------
|
|
10
|
+
python -m torchsparsegradutils.benchmarks.linear_cg_convergence \
|
|
11
|
+
--baseline-ref ea7b8f0 --repeats 100
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import json
|
|
18
|
+
import statistics
|
|
19
|
+
import subprocess
|
|
20
|
+
import time
|
|
21
|
+
import types
|
|
22
|
+
import warnings
|
|
23
|
+
from dataclasses import dataclass
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
from typing import Callable
|
|
26
|
+
|
|
27
|
+
import torch
|
|
28
|
+
|
|
29
|
+
from torchsparsegradutils.utils import linear_cg
|
|
30
|
+
|
|
31
|
+
REPOSITORY_ROOT = Path(__file__).resolve().parents[2]
|
|
32
|
+
LINEAR_CG_PATH = "torchsparsegradutils/utils/linear_cg.py"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True)
|
|
36
|
+
class Problem:
|
|
37
|
+
name: str
|
|
38
|
+
matrix: torch.Tensor
|
|
39
|
+
rhs: torch.Tensor
|
|
40
|
+
tolerance: float
|
|
41
|
+
max_iter: int
|
|
42
|
+
initial_guess: torch.Tensor | None = None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class CountedMatmul:
|
|
46
|
+
def __init__(self, matrix: torch.Tensor):
|
|
47
|
+
self.matrix = matrix
|
|
48
|
+
self.calls = 0
|
|
49
|
+
|
|
50
|
+
def __call__(self, value: torch.Tensor) -> torch.Tensor:
|
|
51
|
+
self.calls += 1
|
|
52
|
+
return self.matrix @ value
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def load_historical_linear_cg(revision: str) -> Callable[..., torch.Tensor]:
|
|
56
|
+
"""Load ``linear_cg`` from a Git revision without changing the worktree."""
|
|
57
|
+
command = ["git", "-C", str(REPOSITORY_ROOT), "show", f"{revision}:{LINEAR_CG_PATH}"]
|
|
58
|
+
try:
|
|
59
|
+
completed = subprocess.run(command, check=True, capture_output=True, text=True)
|
|
60
|
+
except FileNotFoundError as error:
|
|
61
|
+
raise RuntimeError("Cannot load the historical solver because the Git executable was not found") from error
|
|
62
|
+
except subprocess.CalledProcessError as error:
|
|
63
|
+
detail = error.stderr.strip() or "Git could not resolve the requested revision and file"
|
|
64
|
+
raise RuntimeError(
|
|
65
|
+
f"Cannot load the historical solver from revision {revision!r}. "
|
|
66
|
+
f"Run this benchmark from a Git checkout containing that revision. Git reported: {detail}"
|
|
67
|
+
) from error
|
|
68
|
+
module = types.ModuleType("historical_linear_cg")
|
|
69
|
+
exec(compile(completed.stdout, f"{revision}:{LINEAR_CG_PATH}", "exec"), module.__dict__)
|
|
70
|
+
return module.linear_cg
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def make_problems(device: torch.device) -> list[Problem]:
|
|
74
|
+
dtype = torch.float64
|
|
75
|
+
|
|
76
|
+
size = 36
|
|
77
|
+
diagonal = torch.full((size,), 2.0, dtype=dtype, device=device)
|
|
78
|
+
off_diagonal = torch.full((size - 1,), -0.25, dtype=dtype, device=device)
|
|
79
|
+
tight_matrix = torch.diag(diagonal) + torch.diag(off_diagonal, diagonal=1) + torch.diag(off_diagonal, diagonal=-1)
|
|
80
|
+
tight_rhs = torch.zeros(size, dtype=dtype, device=device)
|
|
81
|
+
tight_rhs[size // 2] = 1
|
|
82
|
+
|
|
83
|
+
size = 40
|
|
84
|
+
multiple_matrix = torch.diag(torch.linspace(1.0, 100.0, size, dtype=dtype, device=device))
|
|
85
|
+
multiple_rhs = torch.zeros((size, 101), dtype=dtype, device=device)
|
|
86
|
+
multiple_rhs[:, -1] = 1
|
|
87
|
+
|
|
88
|
+
zero_matrix = torch.diag(torch.tensor([1.0, 2.0, 3.0], dtype=dtype, device=device))
|
|
89
|
+
zero_rhs = torch.zeros(3, dtype=dtype, device=device)
|
|
90
|
+
|
|
91
|
+
return [
|
|
92
|
+
Problem("tight_tolerance", tight_matrix, tight_rhs, tolerance=1e-12, max_iter=200),
|
|
93
|
+
Problem("multiple_rhs", multiple_matrix, multiple_rhs, tolerance=1e-4, max_iter=size),
|
|
94
|
+
Problem(
|
|
95
|
+
"zero_rhs_nonzero_guess",
|
|
96
|
+
zero_matrix,
|
|
97
|
+
zero_rhs,
|
|
98
|
+
tolerance=1e-5,
|
|
99
|
+
max_iter=20,
|
|
100
|
+
initial_guess=torch.ones_like(zero_rhs),
|
|
101
|
+
),
|
|
102
|
+
]
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def synchronize(device: torch.device) -> None:
|
|
106
|
+
if device.type == "cuda":
|
|
107
|
+
torch.cuda.synchronize(device)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def relative_residual_per_rhs(matrix: torch.Tensor, solution: torch.Tensor, rhs: torch.Tensor) -> torch.Tensor:
|
|
111
|
+
if rhs.ndim == 1:
|
|
112
|
+
rhs = rhs.unsqueeze(-1)
|
|
113
|
+
solution = solution.unsqueeze(-1)
|
|
114
|
+
residual_norm = torch.linalg.vector_norm(rhs - matrix @ solution, dim=-2)
|
|
115
|
+
rhs_norm = torch.linalg.vector_norm(rhs, dim=-2)
|
|
116
|
+
return residual_norm / rhs_norm.masked_fill(rhs_norm.eq(0), 1)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def run_once(
|
|
120
|
+
solver: Callable[..., torch.Tensor],
|
|
121
|
+
problem: Problem,
|
|
122
|
+
*,
|
|
123
|
+
fixed: bool,
|
|
124
|
+
) -> tuple[torch.Tensor, int, int, str]:
|
|
125
|
+
matmul = CountedMatmul(problem.matrix)
|
|
126
|
+
arguments = {
|
|
127
|
+
"tolerance": problem.tolerance,
|
|
128
|
+
"max_iter": problem.max_iter,
|
|
129
|
+
"initial_guess": problem.initial_guess,
|
|
130
|
+
}
|
|
131
|
+
with warnings.catch_warnings():
|
|
132
|
+
warnings.simplefilter("ignore", UserWarning)
|
|
133
|
+
if fixed:
|
|
134
|
+
solution, info = solver(matmul, problem.rhs, return_info=True, **arguments)
|
|
135
|
+
return solution, info.iterations, matmul.calls, info.reason
|
|
136
|
+
solution = solver(matmul, problem.rhs, **arguments)
|
|
137
|
+
# The historical solver performs one initial matvec followed by one per
|
|
138
|
+
# iteration and does not recompute the true residual before returning.
|
|
139
|
+
return solution, max(0, matmul.calls - 1), matmul.calls, "not_reported"
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def measure(
|
|
143
|
+
solver: Callable[..., torch.Tensor],
|
|
144
|
+
problem: Problem,
|
|
145
|
+
*,
|
|
146
|
+
fixed: bool,
|
|
147
|
+
warmup: int,
|
|
148
|
+
repeats: int,
|
|
149
|
+
) -> dict[str, object]:
|
|
150
|
+
for _ in range(warmup):
|
|
151
|
+
run_once(solver, problem, fixed=fixed)
|
|
152
|
+
synchronize(problem.rhs.device)
|
|
153
|
+
|
|
154
|
+
durations = []
|
|
155
|
+
for _ in range(repeats):
|
|
156
|
+
start = time.perf_counter()
|
|
157
|
+
run_once(solver, problem, fixed=fixed)
|
|
158
|
+
synchronize(problem.rhs.device)
|
|
159
|
+
durations.append(time.perf_counter() - start)
|
|
160
|
+
|
|
161
|
+
solution, iterations, matvecs, reason = run_once(solver, problem, fixed=fixed)
|
|
162
|
+
true_residual = relative_residual_per_rhs(problem.matrix, solution, problem.rhs)
|
|
163
|
+
reference = torch.linalg.solve(problem.matrix, problem.rhs)
|
|
164
|
+
error = torch.linalg.vector_norm(solution - reference) / torch.linalg.vector_norm(reference).clamp_min(1)
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
"iterations": iterations,
|
|
168
|
+
"matvecs": matvecs,
|
|
169
|
+
"reason": reason,
|
|
170
|
+
"true_relative_residual_max": float(true_residual.max()),
|
|
171
|
+
"unconverged_rhs": int((true_residual > problem.tolerance).sum()),
|
|
172
|
+
"relative_solution_error": float(error),
|
|
173
|
+
"median_time_ms": statistics.median(durations) * 1e3,
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def parse_args() -> argparse.Namespace:
|
|
178
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
179
|
+
parser.add_argument("--baseline-ref", default="ea7b8f0", help="Git revision containing the historical solver")
|
|
180
|
+
parser.add_argument("--device", default="cpu", help="PyTorch device (default: cpu)")
|
|
181
|
+
parser.add_argument("--warmup", type=int, default=5)
|
|
182
|
+
parser.add_argument("--repeats", type=int, default=50)
|
|
183
|
+
arguments = parser.parse_args()
|
|
184
|
+
if arguments.warmup < 0 or arguments.repeats < 1:
|
|
185
|
+
parser.error("--warmup must be nonnegative and --repeats must be positive")
|
|
186
|
+
return arguments
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def main() -> None:
|
|
190
|
+
arguments = parse_args()
|
|
191
|
+
device = torch.device(arguments.device)
|
|
192
|
+
historical_linear_cg = load_historical_linear_cg(arguments.baseline_ref)
|
|
193
|
+
results = {}
|
|
194
|
+
for problem in make_problems(device):
|
|
195
|
+
results[problem.name] = {
|
|
196
|
+
"baseline": measure(
|
|
197
|
+
historical_linear_cg,
|
|
198
|
+
problem,
|
|
199
|
+
fixed=False,
|
|
200
|
+
warmup=arguments.warmup,
|
|
201
|
+
repeats=arguments.repeats,
|
|
202
|
+
),
|
|
203
|
+
"fixed": measure(
|
|
204
|
+
linear_cg,
|
|
205
|
+
problem,
|
|
206
|
+
fixed=True,
|
|
207
|
+
warmup=arguments.warmup,
|
|
208
|
+
repeats=arguments.repeats,
|
|
209
|
+
),
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
payload = {
|
|
213
|
+
"baseline_ref": arguments.baseline_ref,
|
|
214
|
+
"device": str(device),
|
|
215
|
+
"torch_version": torch.__version__,
|
|
216
|
+
"warmup": arguments.warmup,
|
|
217
|
+
"repeats": arguments.repeats,
|
|
218
|
+
"results": results,
|
|
219
|
+
}
|
|
220
|
+
print(json.dumps(payload, indent=2, sort_keys=True))
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
if __name__ == "__main__":
|
|
224
|
+
main()
|
|
@@ -225,8 +225,8 @@ def run_sparse_generic_solve_benchmark():
|
|
|
225
225
|
else:
|
|
226
226
|
residual = A_sparse @ x - B
|
|
227
227
|
|
|
228
|
-
resnorm = torch.
|
|
229
|
-
B_norm = torch.
|
|
228
|
+
resnorm = torch.linalg.vector_norm(residual).cpu().item()
|
|
229
|
+
B_norm = torch.linalg.vector_norm(B).cpu().item()
|
|
230
230
|
relative_resnorm = resnorm / B_norm if B_norm > 0 else 0.0
|
|
231
231
|
|
|
232
232
|
# Print result with residual norm
|
|
@@ -201,10 +201,9 @@ def run_sparse_solve_benchmark():
|
|
|
201
201
|
with torch.no_grad():
|
|
202
202
|
x = alg_fn(A_sparse, B)
|
|
203
203
|
residual = A_sparse @ x - B
|
|
204
|
-
resnorm = torch.
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
)
|
|
204
|
+
resnorm = torch.linalg.vector_norm(residual).cpu().item()
|
|
205
|
+
B_norm = torch.linalg.vector_norm(B).cpu().item()
|
|
206
|
+
relative_resnorm = resnorm / B_norm if B_norm > 0 else 0.0
|
|
208
207
|
|
|
209
208
|
# Print result with residual norm
|
|
210
209
|
print_result_row(
|
|
@@ -27,6 +27,7 @@ from cupyx.scipy.sparse.linalg._solve import spsolve_triangular
|
|
|
27
27
|
from tqdm import tqdm
|
|
28
28
|
|
|
29
29
|
from torchsparsegradutils import sparse_triangular_solve
|
|
30
|
+
from torchsparsegradutils._compat import linalg_solve_triangular_compat
|
|
30
31
|
from torchsparsegradutils.cupy.cupy_sparse_solve import sparse_solve_c4t
|
|
31
32
|
from torchsparsegradutils.utils import rand_sparse, rand_sparse_tri
|
|
32
33
|
|
|
@@ -58,16 +59,19 @@ LAYOUTS = [torch.sparse_coo, torch.sparse_csr]
|
|
|
58
59
|
|
|
59
60
|
ALGORITHMS = [
|
|
60
61
|
(
|
|
61
|
-
"dense.
|
|
62
|
-
lambda A, B: torch.
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
"dense.linalg.solve_triangular",
|
|
63
|
+
lambda A, B: torch.linalg.solve_triangular(
|
|
64
|
+
A.to_dense().transpose(-2, -1) if TRANSPOSE else A.to_dense(),
|
|
65
|
+
B,
|
|
66
|
+
upper=not UPPER if TRANSPOSE else UPPER,
|
|
67
|
+
unitriangular=UNITRIANGULAR,
|
|
68
|
+
),
|
|
65
69
|
),
|
|
66
70
|
(
|
|
67
|
-
"
|
|
68
|
-
lambda A, B:
|
|
69
|
-
|
|
70
|
-
)
|
|
71
|
+
"linalg_solve_triangular_compat",
|
|
72
|
+
lambda A, B: linalg_solve_triangular_compat(
|
|
73
|
+
A, B, upper=UPPER, unitriangular=UNITRIANGULAR, transpose=TRANSPOSE
|
|
74
|
+
),
|
|
71
75
|
),
|
|
72
76
|
(
|
|
73
77
|
"sparse_triangular_solve",
|
|
@@ -153,8 +157,8 @@ def run_sparse_triangular_solve_benchmark():
|
|
|
153
157
|
with torch.no_grad():
|
|
154
158
|
x = alg_fn(A_sparse, B)
|
|
155
159
|
residual = A_sparse @ x - B
|
|
156
|
-
resnorm = torch.
|
|
157
|
-
relative_resnorm = resnorm / torch.
|
|
160
|
+
resnorm = torch.linalg.vector_norm(residual).cpu().item()
|
|
161
|
+
relative_resnorm = resnorm / torch.linalg.vector_norm(B).cpu().item()
|
|
158
162
|
|
|
159
163
|
# # Calculate residual norm for solution accuracy
|
|
160
164
|
# with torch.no_grad():
|
|
@@ -165,8 +169,8 @@ def run_sparse_triangular_solve_benchmark():
|
|
|
165
169
|
# else:
|
|
166
170
|
# Ax = A_sparse @ x
|
|
167
171
|
# residual = Ax - B
|
|
168
|
-
# resnorm = torch.
|
|
169
|
-
# relative_resnorm = resnorm / torch.
|
|
172
|
+
# resnorm = torch.linalg.vector_norm(residual).cpu().item()
|
|
173
|
+
# relative_resnorm = resnorm / torch.linalg.vector_norm(B).cpu().item()
|
|
170
174
|
|
|
171
175
|
# Print result with residual norm
|
|
172
176
|
print_result_row(
|
|
@@ -34,6 +34,7 @@ from cupyx.scipy.sparse.linalg._solve import spsolve_triangular
|
|
|
34
34
|
from tqdm import tqdm
|
|
35
35
|
|
|
36
36
|
from torchsparsegradutils import sparse_triangular_solve
|
|
37
|
+
from torchsparsegradutils._compat import linalg_solve_triangular_compat
|
|
37
38
|
from torchsparsegradutils.cupy.cupy_sparse_solve import sparse_solve_c4t
|
|
38
39
|
|
|
39
40
|
# from jax.lax.linalg import triangular_solve # NOTE: jax doesn't have a sparse triangular solve
|
|
@@ -57,16 +58,19 @@ LAYOUTS = [torch.sparse_coo, torch.sparse_csr]
|
|
|
57
58
|
|
|
58
59
|
ALGORITHMS = [
|
|
59
60
|
(
|
|
60
|
-
"dense.
|
|
61
|
-
lambda A, B: torch.
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
"dense.linalg.solve_triangular",
|
|
62
|
+
lambda A, B: torch.linalg.solve_triangular(
|
|
63
|
+
A.to_dense().transpose(-2, -1) if TRANSPOSE else A.to_dense(),
|
|
64
|
+
B,
|
|
65
|
+
upper=not UPPER if TRANSPOSE else UPPER,
|
|
66
|
+
unitriangular=UNITRIANGULAR,
|
|
67
|
+
),
|
|
64
68
|
),
|
|
65
69
|
(
|
|
66
|
-
"
|
|
67
|
-
lambda A, B:
|
|
68
|
-
|
|
69
|
-
)
|
|
70
|
+
"linalg_solve_triangular_compat",
|
|
71
|
+
lambda A, B: linalg_solve_triangular_compat(
|
|
72
|
+
A, B, upper=UPPER, unitriangular=UNITRIANGULAR, transpose=TRANSPOSE
|
|
73
|
+
),
|
|
70
74
|
),
|
|
71
75
|
(
|
|
72
76
|
"sparse_triangular_solve",
|
|
@@ -185,10 +189,9 @@ def run_triangular_solve_benchmark():
|
|
|
185
189
|
# Use the triangular matrix A_sparse (not A_full) for residual calculation
|
|
186
190
|
# This ensures the residual is computed correctly for the actual triangular system solved
|
|
187
191
|
residual = A_sparse @ x - B
|
|
188
|
-
resnorm = torch.
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
)
|
|
192
|
+
resnorm = torch.linalg.vector_norm(residual).cpu().item()
|
|
193
|
+
B_norm = torch.linalg.vector_norm(B).cpu().item()
|
|
194
|
+
relative_resnorm = resnorm / B_norm if B_norm > 0 else 0.0
|
|
192
195
|
|
|
193
196
|
# Print result
|
|
194
197
|
print_result_row(
|
{torchsparsegradutils-0.2.4 → torchsparsegradutils-0.2.6}/torchsparsegradutils/sparse_solve.py
RENAMED
|
@@ -3,6 +3,7 @@ from typing import Callable, Optional, cast
|
|
|
3
3
|
|
|
4
4
|
import torch
|
|
5
5
|
|
|
6
|
+
from torchsparsegradutils._compat import linalg_solve_triangular_compat
|
|
6
7
|
from torchsparsegradutils.utils import convert_coo_to_csr, sparse_block_diag, sparse_block_diag_split, stack_csr
|
|
7
8
|
|
|
8
9
|
|
|
@@ -154,7 +155,7 @@ class SparseTriangularSolve(torch.autograd.Function):
|
|
|
154
155
|
See Also
|
|
155
156
|
--------
|
|
156
157
|
sparse_triangular_solve : User-facing function that calls this autograd function.
|
|
157
|
-
torch.
|
|
158
|
+
torch.linalg.solve_triangular : PyTorch's native dense triangular solver.
|
|
158
159
|
"""
|
|
159
160
|
|
|
160
161
|
@staticmethod
|
|
@@ -177,11 +178,9 @@ class SparseTriangularSolve(torch.autograd.Function):
|
|
|
177
178
|
A = convert_coo_to_csr(A) # NOTE: triangular solve doesn't work with sparse coo
|
|
178
179
|
ctx.csr = False
|
|
179
180
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
B.detach(), A.detach(), upper=upper, unitriangular=unitriangular, transpose=transpose
|
|
184
|
-
).solution
|
|
181
|
+
x = linalg_solve_triangular_compat(
|
|
182
|
+
A.detach(), B.detach(), upper=upper, unitriangular=unitriangular, transpose=transpose
|
|
183
|
+
)
|
|
185
184
|
|
|
186
185
|
x.requires_grad = grad_flag
|
|
187
186
|
ctx.save_for_backward(A, x.detach())
|
|
@@ -199,11 +198,10 @@ class SparseTriangularSolve(torch.autograd.Function):
|
|
|
199
198
|
A, x = ctx.saved_tensors
|
|
200
199
|
|
|
201
200
|
# Backprop rule: gradB = A^{-T} grad
|
|
202
|
-
# NOTE: DEPRECATED: Check if a workaround for https://github.com/pytorch/pytorch/issues/88890 is needed
|
|
203
201
|
|
|
204
|
-
gradB =
|
|
205
|
-
|
|
206
|
-
)
|
|
202
|
+
gradB = linalg_solve_triangular_compat(
|
|
203
|
+
A, grad, upper=ctx.upper, transpose=not ctx.transpose, unitriangular=ctx.unitriangular
|
|
204
|
+
)
|
|
207
205
|
|
|
208
206
|
# The gradient with respect to the matrix A seen as a dense matrix would
|
|
209
207
|
# lead to a backprop rule as follows
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Regression checks for deprecated upstream PyTorch APIs."""
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
import re
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
REPOSITORY_ROOT = Path(__file__).parents[2]
|
|
8
|
+
PACKAGE_ROOT = REPOSITORY_ROOT / "torchsparsegradutils"
|
|
9
|
+
COMPATIBILITY_MODULE = Path("_compat.py")
|
|
10
|
+
DEPRECATED_DOCUMENTATION_NORM = re.compile(r"(?<![\w.])torch\.norm\s*\(")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def attribute_name(node):
|
|
14
|
+
parts = []
|
|
15
|
+
while isinstance(node, ast.Attribute):
|
|
16
|
+
parts.append(node.attr)
|
|
17
|
+
node = node.value
|
|
18
|
+
if isinstance(node, ast.Name):
|
|
19
|
+
parts.append(node.id)
|
|
20
|
+
return ".".join(reversed(parts))
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_deprecated_torch_calls_are_not_reintroduced():
|
|
24
|
+
violations = []
|
|
25
|
+
compatibility_calls = []
|
|
26
|
+
|
|
27
|
+
for path in PACKAGE_ROOT.rglob("*.py"):
|
|
28
|
+
relative_path = path.relative_to(PACKAGE_ROOT)
|
|
29
|
+
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
|
|
30
|
+
|
|
31
|
+
for node in ast.walk(tree):
|
|
32
|
+
location = f"{relative_path}:{getattr(node, 'lineno', '?')}"
|
|
33
|
+
if isinstance(node, ast.Attribute) and attribute_name(node) == "torch.jit.script":
|
|
34
|
+
violations.append(location)
|
|
35
|
+
|
|
36
|
+
if not isinstance(node, ast.Call) or not isinstance(node.func, ast.Attribute):
|
|
37
|
+
continue
|
|
38
|
+
|
|
39
|
+
call_name = attribute_name(node.func)
|
|
40
|
+
if call_name == "torch.norm":
|
|
41
|
+
violations.append(location)
|
|
42
|
+
elif call_name == "torch.triangular_solve":
|
|
43
|
+
if relative_path == COMPATIBILITY_MODULE:
|
|
44
|
+
compatibility_calls.append(location)
|
|
45
|
+
else:
|
|
46
|
+
violations.append(location)
|
|
47
|
+
|
|
48
|
+
assert not violations, "Deprecated PyTorch APIs found:\n" + "\n".join(violations)
|
|
49
|
+
assert len(compatibility_calls) == 1, "Expected one isolated sparse triangular compatibility call"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_documentation_does_not_use_deprecated_norm_apis():
|
|
53
|
+
violations = []
|
|
54
|
+
documentation_paths = [REPOSITORY_ROOT / "README.md"]
|
|
55
|
+
documentation_paths.extend((REPOSITORY_ROOT / "docs").rglob("*.md"))
|
|
56
|
+
documentation_paths.extend((REPOSITORY_ROOT / "docs").rglob("*.rst"))
|
|
57
|
+
|
|
58
|
+
for path in documentation_paths:
|
|
59
|
+
if DEPRECATED_DOCUMENTATION_NORM.search(path.read_text(encoding="utf-8")):
|
|
60
|
+
violations.append(str(path.relative_to(REPOSITORY_ROOT)))
|
|
61
|
+
|
|
62
|
+
assert not violations, "Deprecated norm examples found:\n" + "\n".join(violations)
|