torchsparsegradutils 0.2.2__tar.gz → 0.2.3__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 (77) hide show
  1. {torchsparsegradutils-0.2.2/torchsparsegradutils.egg-info → torchsparsegradutils-0.2.3}/PKG-INFO +1 -1
  2. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/docs/source/conf.py +2 -2
  3. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/pyproject.toml +1 -1
  4. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/sparse_solve.py +9 -2
  5. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/tests/test_jax_bindings.py +1 -5
  6. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/tests/test_sparse_solve.py +222 -0
  7. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3/torchsparsegradutils.egg-info}/PKG-INFO +1 -1
  8. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/LICENSE +0 -0
  9. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/MANIFEST.in +0 -0
  10. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/README.md +0 -0
  11. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/setup.cfg +0 -0
  12. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/setup.py +0 -0
  13. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/__init__.py +0 -0
  14. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/benchmarks/__init__.py +0 -0
  15. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/benchmarks/batched_sparse_mm_rand.py +0 -0
  16. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/benchmarks/benchmark_suite.py +0 -0
  17. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/benchmarks/benchmark_utils.py +0 -0
  18. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/benchmarks/sparse_generic_solve_rand.py +0 -0
  19. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/benchmarks/sparse_generic_solve_suite.py +0 -0
  20. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/benchmarks/sparse_mm_rand.py +0 -0
  21. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/benchmarks/sparse_mm_suite.py +0 -0
  22. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/benchmarks/sparse_triangular_solve_rand.py +0 -0
  23. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/benchmarks/sparse_triangular_solve_suitesparse.py +0 -0
  24. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/benchmarks/visualize_benchmark_results.py +0 -0
  25. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/cupy/__init__.py +0 -0
  26. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/cupy/cupy_bindings.py +0 -0
  27. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/cupy/cupy_sparse_solve.py +0 -0
  28. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/distributions/__init__.py +0 -0
  29. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/distributions/constraints.py +0 -0
  30. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/distributions/sparse_multivariate_normal.py +0 -0
  31. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/encoders/__init__.py +0 -0
  32. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/encoders/pairwise_encoder.py +0 -0
  33. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/encoders/pairwise_voxel_encoder.py +0 -0
  34. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/indexed_matmul.py +0 -0
  35. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/jax/__init__.py +0 -0
  36. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/jax/jax_bindings.py +0 -0
  37. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/jax/jax_sparse_solve.py +0 -0
  38. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/sparse_lstsq.py +0 -0
  39. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/sparse_matmul.py +0 -0
  40. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/tests/__init__.py +0 -0
  41. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/tests/conftest.py +0 -0
  42. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/tests/test_bicgstab.py +0 -0
  43. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/tests/test_config.py +0 -0
  44. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/tests/test_cupy_bindings.py +0 -0
  45. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/tests/test_cupy_sparse_solve.py +0 -0
  46. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/tests/test_dist_stats_helpers.py +0 -0
  47. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/tests/test_distributions.py +0 -0
  48. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/tests/test_doctests.py +0 -0
  49. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/tests/test_encoders.py +0 -0
  50. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/tests/test_indexed_matmul.py +0 -0
  51. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/tests/test_integration_pairwise_sparse_mvn.py +0 -0
  52. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/tests/test_jax_sparse_solve.py +0 -0
  53. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/tests/test_linear_cg.py +0 -0
  54. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/tests/test_lsmr.py +0 -0
  55. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/tests/test_minres.py +0 -0
  56. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/tests/test_params/czyx_shifts.yaml +0 -0
  57. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/tests/test_params/pairwise_coo_indices.yaml +0 -0
  58. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/tests/test_params/xyz_coords.yaml +0 -0
  59. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/tests/test_quickstart_guide.py +0 -0
  60. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/tests/test_random.py +0 -0
  61. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/tests/test_sparse_lstsq.py +0 -0
  62. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/tests/test_sparse_matmul.py +0 -0
  63. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/tests/test_sparse_triangular_solve.py +0 -0
  64. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/tests/test_utils.py +0 -0
  65. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/utils/__init__.py +0 -0
  66. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/utils/bicgstab.py +0 -0
  67. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/utils/dist_stats_helpers.py +0 -0
  68. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/utils/linear_cg.py +0 -0
  69. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/utils/lsmr.py +0 -0
  70. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/utils/minres.py +0 -0
  71. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/utils/random_sparse.py +0 -0
  72. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils/utils/utils.py +0 -0
  73. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils.egg-info/SOURCES.txt +0 -0
  74. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils.egg-info/dependency_links.txt +0 -0
  75. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils.egg-info/not-zip-safe +0 -0
  76. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils.egg-info/requires.txt +0 -0
  77. {torchsparsegradutils-0.2.2 → torchsparsegradutils-0.2.3}/torchsparsegradutils.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: torchsparsegradutils
3
- Version: 0.2.2
3
+ Version: 0.2.3
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
@@ -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.2"
19
- version = "0.2.2"
18
+ release = "0.2.3"
19
+ version = "0.2.3"
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.2"
7
+ version = "0.2.3"
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"
@@ -438,6 +438,7 @@ class SparseGenericSolve(torch.autograd.Function):
438
438
  @staticmethod
439
439
  def forward(ctx, A, B, solve, transpose_solve, kwargs):
440
440
  grad_flag = A.requires_grad or B.requires_grad
441
+ ctx.solve = solve
441
442
  ctx.transpose_solve = transpose_solve
442
443
  ctx.kwargs = kwargs # Store kwargs for backward pass
443
444
 
@@ -449,7 +450,7 @@ class SparseGenericSolve(torch.autograd.Function):
449
450
 
450
451
  x.requires_grad = grad_flag
451
452
 
452
- ctx.save_for_backward(A, x.detach())
453
+ ctx.save_for_backward(A, x)
453
454
  return x
454
455
 
455
456
  @staticmethod
@@ -463,7 +464,13 @@ class SparseGenericSolve(torch.autograd.Function):
463
464
  grad = grad.unsqueeze(-1)
464
465
 
465
466
  # Backprop rule: gradB = A^{-T} grad
466
- gradB = ctx.transpose_solve(A, grad, **ctx.kwargs)
467
+ gradB = sparse_generic_solve(
468
+ A,
469
+ grad,
470
+ solve=ctx.transpose_solve,
471
+ transpose_solve=ctx.solve,
472
+ **ctx.kwargs,
473
+ )
467
474
 
468
475
  # Ensure gradient dtype matches input dtype
469
476
  if gradB.dtype != A.dtype:
@@ -1,19 +1,15 @@
1
- import jax
2
1
  import numpy as np
3
2
  import pytest
4
3
  import torch
5
4
  from test_config import DEVICES
6
5
 
7
- import torchsparsegradutils as tsgu
8
6
  import torchsparsegradutils.jax as tsgujax
9
7
 
10
8
  # skip if JAX unavailable
11
- pytest.importorskip("jax")
9
+ jax = pytest.importorskip("jax")
12
10
  if not tsgujax.have_jax:
13
11
  pytest.skip("JAX bindings unavailable, skipping jax tests", allow_module_level=True)
14
12
 
15
- import jax.numpy as jnp
16
-
17
13
 
18
14
  def _id_device(d):
19
15
  return str(d)
@@ -42,6 +42,132 @@ def solve_id(solve):
42
42
  return "default"
43
43
 
44
44
 
45
+ def _make_differentiable_tridiag_spd(theta, layout):
46
+ """Create a small sparse SPD matrix with fixed sparsity and differentiable values."""
47
+ n = theta.numel()
48
+ device = theta.device
49
+
50
+ diag = theta.square() + 2.0
51
+ off = -0.1 * torch.sigmoid(theta[:-1])
52
+
53
+ rows = torch.cat(
54
+ [
55
+ torch.arange(n, device=device),
56
+ torch.arange(n - 1, device=device),
57
+ torch.arange(1, n, device=device),
58
+ ]
59
+ )
60
+ cols = torch.cat(
61
+ [
62
+ torch.arange(n, device=device),
63
+ torch.arange(1, n, device=device),
64
+ torch.arange(n - 1, device=device),
65
+ ]
66
+ )
67
+ values = torch.cat([diag, off, off])
68
+
69
+ A = torch.sparse_coo_tensor(torch.stack([rows, cols]), values, (n, n)).coalesce()
70
+ if layout == torch.sparse_csr:
71
+ A = A.to_sparse_csr()
72
+ return A, A.to_dense()
73
+
74
+
75
+ def _make_differentiable_nonsymmetric_tridiag(theta, layout):
76
+ """Create a small fixed-sparsity non-symmetric diagonally dominant matrix."""
77
+ n = 6
78
+ device = theta.device
79
+ dtype = theta.dtype
80
+
81
+ if theta.numel() != 3 * n - 2:
82
+ raise ValueError(f"theta should have length {3 * n - 2}, got {theta.numel()}")
83
+
84
+ diag_theta = theta[:n]
85
+ upper_theta = theta[n : n + n - 1]
86
+ lower_theta = theta[n + n - 1 :]
87
+
88
+ diag = diag_theta.square() + 3.0
89
+ upper = 0.05 * torch.tanh(upper_theta)
90
+ lower = -0.08 * torch.sigmoid(lower_theta)
91
+
92
+ rows = torch.cat(
93
+ [
94
+ torch.arange(n, device=device),
95
+ torch.arange(n - 1, device=device),
96
+ torch.arange(1, n, device=device),
97
+ ]
98
+ )
99
+ cols = torch.cat(
100
+ [
101
+ torch.arange(n, device=device),
102
+ torch.arange(1, n, device=device),
103
+ torch.arange(n - 1, device=device),
104
+ ]
105
+ )
106
+ values = torch.cat([diag, upper, lower]).to(dtype=dtype)
107
+
108
+ A = torch.sparse_coo_tensor(torch.stack([rows, cols]), values, (n, n)).coalesce()
109
+ if layout == torch.sparse_csr:
110
+ A = A.to_sparse_csr()
111
+ return A, A.to_dense()
112
+
113
+
114
+ def _bicgstab_transpose(A, B, **kwargs):
115
+ """Solve A.T X = B using BiCGSTAB."""
116
+ if A.layout == torch.sparse_csr:
117
+ # A.T currently triggers aten::as_strided for sparse CSR tensors in PyTorch,
118
+ # so use transpose(...).to_sparse_csr() for the CSR test case.
119
+ # A.transpose(0, 1) for csr will return csc, hence the to_sparse_csr() call.
120
+ return bicgstab(A.transpose(0, 1).to_sparse_csr(), B, **kwargs)
121
+ return bicgstab(A.T, B, **kwargs)
122
+
123
+
124
+ def _bicgstab_higher_order_kwargs(value_dtype):
125
+ from torchsparsegradutils.utils.bicgstab import BICGSTABSettings
126
+
127
+ if value_dtype == torch.float32:
128
+ return {"settings": BICGSTABSettings(reltol=1e-6, abstol=1e-6, matvec_max=1000)}
129
+ return {"settings": BICGSTABSettings(reltol=1e-12, abstol=1e-12, matvec_max=1000)}
130
+
131
+
132
+ def _higher_order_bicgstab_tolerances(value_dtype):
133
+ if value_dtype == torch.float32:
134
+ return {
135
+ "output": (1e-5, 1e-5),
136
+ "grad": (5e-5, 5e-5),
137
+ "hess": (5e-3, 5e-3),
138
+ }
139
+ return {
140
+ "output": (1e-8, 1e-8),
141
+ "grad": (1e-6, 1e-6),
142
+ "hess": (5e-5, 5e-5),
143
+ }
144
+
145
+
146
+ def _settings_for_higher_order_solve(solve, value_dtype):
147
+ if solve is linear_cg:
148
+ from torchsparsegradutils.utils.linear_cg import LinearCGSettings
149
+
150
+ return {"settings": LinearCGSettings(cg_tolerance=1e-5, max_cg_iterations=1000)}
151
+ if solve is minres:
152
+ from torchsparsegradutils.utils.minres import MINRESSettings
153
+
154
+ tolerance = 1e-6 if value_dtype == torch.float32 else 1e-10
155
+ return {"settings": MINRESSettings(minres_tolerance=tolerance, max_cg_iterations=1000)}
156
+ return {}
157
+
158
+
159
+ def _higher_order_spd_tolerances(value_dtype):
160
+ if value_dtype == torch.float32:
161
+ return {
162
+ "grad": (1e-3, 1e-3),
163
+ "hess": (5e-2, 5e-2),
164
+ }
165
+ return {
166
+ "grad": (1e-5, 1e-5),
167
+ "hess": (5e-4, 5e-4),
168
+ }
169
+
170
+
45
171
  # Define Fixtures
46
172
 
47
173
 
@@ -260,3 +386,99 @@ def test_kwargs_with_different_solvers_same_matrix():
260
386
  # All solutions should be close to each other
261
387
  assert torch.allclose(X_cg, X_minres, atol=atol, rtol=rtol)
262
388
  assert torch.allclose(X_bicgstab, X_minres, atol=atol, rtol=rtol)
389
+
390
+
391
+ @pytest.mark.parametrize("base_solve", [linear_cg, minres], ids=[solve_id(linear_cg), solve_id(minres)])
392
+ def test_sparse_generic_solve_higher_order_create_graph_no_out_error(layout, base_solve, device, value_dtype):
393
+ torch.manual_seed(0)
394
+
395
+ theta = torch.randn(8, dtype=value_dtype, device=device, requires_grad=True)
396
+ A, _ = _make_differentiable_tridiag_spd(theta, layout)
397
+ B = torch.randn(8, 2, dtype=value_dtype, device=device)
398
+
399
+ kwargs = _settings_for_higher_order_solve(base_solve, value_dtype)
400
+ loss = sparse_generic_solve(A, B, solve=base_solve, transpose_solve=base_solve, **kwargs).sum()
401
+
402
+ grad_theta = torch.autograd.grad(loss, theta, create_graph=True)[0]
403
+
404
+ assert grad_theta.requires_grad
405
+ assert torch.isfinite(grad_theta).all()
406
+
407
+ second = torch.autograd.grad(grad_theta.sum(), theta)[0]
408
+ assert torch.isfinite(second).all()
409
+
410
+
411
+ @pytest.mark.parametrize("base_solve", [linear_cg, minres], ids=[solve_id(linear_cg), solve_id(minres)])
412
+ def test_sparse_generic_solve_higher_order_matches_dense_reference(layout, base_solve, device, value_dtype):
413
+ torch.manual_seed(1)
414
+
415
+ theta_sparse = torch.randn(6, dtype=value_dtype, device=device, requires_grad=True)
416
+ theta_dense = theta_sparse.detach().clone().requires_grad_()
417
+
418
+ B = torch.randn(6, 2, dtype=value_dtype, device=device)
419
+
420
+ A_sparse, _ = _make_differentiable_tridiag_spd(theta_sparse, layout)
421
+ _, A_dense = _make_differentiable_tridiag_spd(theta_dense, torch.sparse_coo)
422
+
423
+ kwargs = _settings_for_higher_order_solve(base_solve, value_dtype)
424
+ tolerances = _higher_order_spd_tolerances(value_dtype)
425
+
426
+ out_sparse = sparse_generic_solve(A_sparse, B, solve=base_solve, transpose_solve=base_solve, **kwargs)
427
+ out_dense = torch.linalg.solve(A_dense, B)
428
+
429
+ loss_sparse = out_sparse.square().sum()
430
+ loss_dense = out_dense.square().sum()
431
+
432
+ grad_sparse = torch.autograd.grad(loss_sparse, theta_sparse, create_graph=True)[0]
433
+ grad_dense = torch.autograd.grad(loss_dense, theta_dense, create_graph=True)[0]
434
+
435
+ hess_vec_sparse = torch.autograd.grad(grad_sparse.sum(), theta_sparse)[0]
436
+ hess_vec_dense = torch.autograd.grad(grad_dense.sum(), theta_dense)[0]
437
+
438
+ grad_atol, grad_rtol = tolerances["grad"]
439
+ hess_atol, hess_rtol = tolerances["hess"]
440
+ assert torch.allclose(grad_sparse, grad_dense, atol=grad_atol, rtol=grad_rtol)
441
+ assert torch.allclose(hess_vec_sparse, hess_vec_dense, atol=hess_atol, rtol=hess_rtol)
442
+
443
+
444
+ def test_sparse_generic_solve_higher_order_nonsymmetric_bicgstab_matches_dense_reference(layout, device, value_dtype):
445
+ torch.manual_seed(2)
446
+
447
+ n = 6
448
+ theta_sparse = torch.randn(3 * n - 2, dtype=value_dtype, device=device, requires_grad=True)
449
+ theta_dense = theta_sparse.detach().clone().requires_grad_()
450
+
451
+ B = torch.randn(n, 2, dtype=value_dtype, device=device)
452
+
453
+ A_sparse, _ = _make_differentiable_nonsymmetric_tridiag(theta_sparse, layout)
454
+ _, A_dense = _make_differentiable_nonsymmetric_tridiag(theta_dense, torch.sparse_coo)
455
+ assert not torch.allclose(A_dense, A_dense.T)
456
+
457
+ kwargs = _bicgstab_higher_order_kwargs(value_dtype)
458
+ tolerances = _higher_order_bicgstab_tolerances(value_dtype)
459
+
460
+ out_sparse = sparse_generic_solve(
461
+ A_sparse,
462
+ B,
463
+ solve=bicgstab,
464
+ transpose_solve=_bicgstab_transpose,
465
+ **kwargs,
466
+ )
467
+ out_dense = torch.linalg.solve(A_dense, B)
468
+ output_atol, output_rtol = tolerances["output"]
469
+ assert torch.allclose(A_dense @ out_sparse, B, atol=output_atol, rtol=output_rtol)
470
+
471
+ loss_sparse = out_sparse.square().sum()
472
+ loss_dense = out_dense.square().sum()
473
+
474
+ grad_sparse = torch.autograd.grad(loss_sparse, theta_sparse, create_graph=True)[0]
475
+ grad_dense = torch.autograd.grad(loss_dense, theta_dense, create_graph=True)[0]
476
+
477
+ hess_vec_sparse = torch.autograd.grad(grad_sparse.sum(), theta_sparse)[0]
478
+ hess_vec_dense = torch.autograd.grad(grad_dense.sum(), theta_dense)[0]
479
+
480
+ grad_atol, grad_rtol = tolerances["grad"]
481
+ hess_atol, hess_rtol = tolerances["hess"]
482
+ assert torch.allclose(out_sparse, out_dense, atol=output_atol, rtol=output_rtol)
483
+ assert torch.allclose(grad_sparse, grad_dense, atol=grad_atol, rtol=grad_rtol)
484
+ assert torch.allclose(hess_vec_sparse, hess_vec_dense, atol=hess_atol, rtol=hess_rtol)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: torchsparsegradutils
3
- Version: 0.2.2
3
+ Version: 0.2.3
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