sparse-kappa 0.0.2__tar.gz → 0.0.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 (50) hide show
  1. {sparse_kappa-0.0.2/sparse_kappa.egg-info → sparse_kappa-0.0.3}/PKG-INFO +93 -28
  2. {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/README.md +89 -25
  3. {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/pyproject.toml +5 -4
  4. {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/setup.py +4 -3
  5. {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/sparse_kappa/__init__.py +8 -4
  6. sparse_kappa-0.0.3/sparse_kappa/backend/__init__.py +6 -0
  7. sparse_kappa-0.0.3/sparse_kappa/backend/sparse/__init__.py +188 -0
  8. sparse_kappa-0.0.3/sparse_kappa/backend/sparse/linalg.py +127 -0
  9. sparse_kappa-0.0.3/sparse_kappa/backend/torch_api.py +337 -0
  10. {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/sparse_kappa/config.py +1 -1
  11. sparse_kappa-0.0.3/sparse_kappa/gnn/__init__.py +20 -0
  12. sparse_kappa-0.0.3/sparse_kappa/gnn/data.py +108 -0
  13. sparse_kappa-0.0.3/sparse_kappa/gnn/features.py +161 -0
  14. sparse_kappa-0.0.3/sparse_kappa/gnn/models.py +113 -0
  15. sparse_kappa-0.0.3/sparse_kappa/gnn/training.py +323 -0
  16. {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/sparse_kappa/norm1/__init__.py +2 -1
  17. {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/sparse_kappa/norm1/hager.py +2 -2
  18. sparse_kappa-0.0.3/sparse_kappa/norm1/hager_higham.py +367 -0
  19. {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/sparse_kappa/norm1/inverse_norm.py +2 -2
  20. {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/sparse_kappa/norm1/monte_carlo.py +2 -2
  21. {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/sparse_kappa/norm1/oettli_prager.py +4 -4
  22. {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/sparse_kappa/norm1/power_iteration.py +2 -2
  23. {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/sparse_kappa/norm2/__init__.py +6 -3
  24. {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/sparse_kappa/norm2/golub_kahan.py +5 -5
  25. {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/sparse_kappa/norm2/lanczos.py +6 -6
  26. sparse_kappa-0.0.3/sparse_kappa/norm2/lanczos_unsym.py +171 -0
  27. sparse_kappa-0.0.3/sparse_kappa/norm2/power_method.py +422 -0
  28. sparse_kappa-0.0.3/sparse_kappa/norm2/svds.py +129 -0
  29. sparse_kappa-0.0.2/sparse_kappa/norm2/cupy_wrappers.py → sparse_kappa-0.0.3/sparse_kappa/norm2/torch_wrappers.py +12 -156
  30. sparse_kappa-0.0.3/sparse_kappa/notimplement.py +171 -0
  31. {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/sparse_kappa/solvers.py +29 -22
  32. {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/sparse_kappa/sparse_kappa.py +13 -12
  33. {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/sparse_kappa/utils.py +4 -4
  34. {sparse_kappa-0.0.2 → sparse_kappa-0.0.3/sparse_kappa.egg-info}/PKG-INFO +93 -28
  35. {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/sparse_kappa.egg-info/SOURCES.txt +27 -4
  36. {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/sparse_kappa.egg-info/requires.txt +2 -1
  37. {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/tests/test_1_norm_cond.py +5 -6
  38. {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/tests/test_accuracy.py +2 -2
  39. {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/tests/test_bicgstab_solver.py +2 -2
  40. sparse_kappa-0.0.3/tests/test_gnn.py +61 -0
  41. {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/tests/test_norm1.py +2 -2
  42. {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/tests/test_norm2.py +2 -2
  43. {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/tests/test_speed.py +8 -8
  44. sparse_kappa-0.0.2/sparse_kappa/norm1/hager_higham.py +0 -308
  45. sparse_kappa-0.0.2/sparse_kappa/norm2/arnoldi.py +0 -112
  46. sparse_kappa-0.0.2/sparse_kappa/norm2/power_method.py +0 -338
  47. {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/LICENSE +0 -0
  48. {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/setup.cfg +0 -0
  49. {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/sparse_kappa.egg-info/dependency_links.txt +0 -0
  50. {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/sparse_kappa.egg-info/top_level.txt +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sparse-kappa
3
- Version: 0.0.2
4
- Summary: GPU-accelerated sparse matrix condition number estimation using CuPy
3
+ Version: 0.0.3
4
+ Summary: PyTorch-accelerated sparse matrix condition number estimation
5
5
  Home-page: https://github.com/chenxinye/sparse-kappa
6
6
  Author: Xinye Chen
7
7
  Author-email: Xinye Chen <xinyechenai@gmail.com>
@@ -19,8 +19,9 @@ Classifier: Programming Language :: Python :: 3.11
19
19
  Requires-Python: >=3.8
20
20
  Description-Content-Type: text/markdown
21
21
  License-File: LICENSE
22
- Requires-Dist: cupy>=10.0.0
23
22
  Requires-Dist: numpy>=1.20.0
23
+ Requires-Dist: torch>=2.0.0
24
+ Requires-Dist: myst-parser>=2.0
24
25
  Provides-Extra: dev
25
26
  Requires-Dist: pytest>=6.0; extra == "dev"
26
27
  Requires-Dist: pytest-cov>=2.0; extra == "dev"
@@ -35,28 +36,31 @@ Dynamic: requires-python
35
36
 
36
37
  # sparse-kappa
37
38
 
38
- **Condition Number Estimation on GPUs for Sparse Matrices**
39
+ [![!pypi](https://img.shields.io/pypi/v/sparse-kappa?color=indigo)](https://pypi.org/project/sparse-kappa/)
40
+ [![License: MIT](https://anaconda.org/conda-forge/sparse-kappaclustering/badges/license.svg)]([https://github.com/nla-group/sparse-kappa/blob/master/LICENSE](https://opensource.org/licenses/MIT))
41
+ [![Documentation Status](https://readthedocs.org/projects/sparse-kappa/badge/?version=latest)](https://sparse-kappa.readthedocs.io/en/latest/)
39
42
 
40
43
 
41
-
42
- </div>
44
+ **Condition Number Estimation on CPUs/GPUs for Sparse Matrices**
43
45
 
44
46
 
45
- ``sparse-kappa`` is a GPU-accelerated library for estimating condition numbers of sparse matrices using CuPy. It supports a variaty of estimation method associated with linear solvers. ``sparse-kappa`` is designed for benchmarking condition number estimator and practical use in science and engineering community.
46
47
 
48
+ </div>
47
49
 
48
50
 
51
+ ``sparse-kappa`` is a GPU-accelerated library for estimating condition numbers of sparse matrices using PyTorch. It supports a variaty of estimation method associated with linear solvers. ``sparse-kappa`` is designed for benchmarking condition number estimator and practical use in science and engineering community.
49
52
 
50
53
 
51
54
  ## Features
52
55
 
53
- - **GPU-Accelerated**: All computations run on NVIDIA GPUs via CuPy
56
+ - **GPU-Accelerated**: All computations run on NVIDIA GPUs via PyTorch
54
57
  - **Multiple Norms**: Support for 1-norm and 2-norm condition numbers
55
58
  - **Rich Algorithm Suite**:
56
59
  - **1-norm**: Hager-Higham, Power iteration, Oettli-Prager sampling, Block Hager
57
- - **2-norm**: Power method, Lanczos, Arnoldi, Golub-Kahan bidiagonalization
58
- - **CuPy integrations**: SVDS, EIGSH, LOBPCG wrappers
60
+ - **2-norm**: Power method, Lanczos, Golub-Kahan bidiagonalization
61
+ - **PyTorch integrations**: SVDS, EIGSH, LOBPCG wrappers
59
62
  - **Flexible Solver System**: LU, LSMR, CG, GMRES, Direct, Auto-selection
63
+ - **GNN Prediction Module**: Train reusable models that predict condition numbers directly or via inverse-norm prediction
60
64
  - **Smart LU Caching**: Reuses factorizations for multiple solves (10-20x speedup)
61
65
  - **Memory Efficient**: Designed for large sparse matrices
62
66
 
@@ -71,14 +75,14 @@ pip install sparse-kappa
71
75
 
72
76
  ```bash
73
77
  git clone https://github.com/chenxinye/sparse-kappa
74
- pip install cupy-cuda11x # or cupy-cuda12x for CUDA 12
78
+ pip install torch
75
79
  pip install -e .
76
80
  ```
77
81
 
78
82
  ## Quick Start
79
83
 
80
84
  ```python
81
- import cupyx.scipy.sparse as sp
85
+ from sparse_kappa.backend import sparse as sp
82
86
  from sparse_kappa import cond_estimate
83
87
 
84
88
  # Create sparse matrix
@@ -107,16 +111,16 @@ cond = cond_estimate(A, norm=1, method='hager-higham', solver='lu')
107
111
 
108
112
  ## 2-Norm Methods
109
113
 
110
- | Method | Description | Best For | Complexity |
111
- |--------------|------------------------------------|------------------------------|-----------------|
112
- | `svds` | Partial SVD (most accurate) | Small-medium matrices (<5k) | O(k·nnz) |
113
- | `eigsh` | Symmetric eigenvalue solver | Symmetric matrices | O(k·nnz) |
114
- | `lobpcg` | Block preconditioned CG | Large matrices | O(k·nnz) |
115
- | `power` | Power iteration | Quick estimates | O(k·nnz) |
116
- | `lanczos` | Lanczos tridiagonalization | Medium matrices | O(k²·nnz) |
117
- | `arnoldi` | Arnoldi iteration | Non-symmetric | O(k²·nnz) |
118
- | `golub-kahan`| Bidiagonalization | Numerically stable | O(k·nnz) |
119
- | `auto` | Automatic selection | All cases | - |
114
+ | Method | Description | Best For | Complexity |
115
+ |-------------------|------------------------------------------------------------|-----------------------------------------|-----------------|
116
+ | `svds` | Partial SVD (most accurate) | Small-medium matrices (<5k) | O(k·nnz) |
117
+ | `eigsh` | Symmetric eigenvalue solver | Symmetric / Hermitian matrices | O(k·nnz) |
118
+ | `lobpcg` | Block preconditioned CG | Large matrices | O(k·nnz) |
119
+ | `power` | Power iteration | Quick estimates | O(k·nnz) |
120
+ | `lanczos` | Lanczos tridiagonalization | Medium symmetric matrices | O(k²·nnz) |
121
+ | `lanczos_unsym` | Lanczos-style condition estimation via `eigsh` on `A^H A` | Non-symmetric / rectangular matrices | O(k·nnz) |
122
+ | `golub-kahan` | Bidiagonalization | Numerically stable | O(k·nnz) |
123
+ | `auto` | Automatic selection | All cases | - |
120
124
 
121
125
  ## Solver Options
122
126
 
@@ -135,12 +139,70 @@ All 1-norm methods support flexible solver selection:
135
139
  **Legend:**
136
140
  `k` = iterations, `b` = block size, `m` = samples, `nnz` = non-zeros
137
141
 
142
+ ## GNN-Based Prediction
143
+
144
+ The `sparse_kappa.gnn` module learns a mapping from sparse matrices to
145
+ condition-number related scalars. It supports two training targets:
146
+
147
+ - `target="condition"`: predict `kappa(A)` directly.
148
+ - `target="inverse_norm"`: predict `||A^{-1}||`, then compute `||A|| * ||A^{-1}||` at inference time.
149
+
150
+ The default graph builder turns a sparse matrix into a row/column bipartite
151
+ graph. You can replace the feature extractor, model, optimizer, scheduler,
152
+ loss function, and validation callback.
153
+
154
+ ```python
155
+ from sparse_kappa import TrainingConfig, train_gnn_condition_estimator
156
+ from sparse_kappa.gnn import GNNConditionEstimator
157
+ from sparse_kappa.backend import sparse as sp
158
+
159
+ train_samples = [
160
+ {"matrix": A0, "condition_number": 12.3, "norm_Ainv": 0.42},
161
+ {"matrix": A1, "condition_number": 18.9, "norm_Ainv": 0.61},
162
+ ]
163
+
164
+ # Scheme 1: direct condition-number prediction
165
+ config = TrainingConfig(target="condition", norm=2, epochs=100, lr=1e-3)
166
+ estimator = train_gnn_condition_estimator(
167
+ train_samples,
168
+ val_data=None,
169
+ config=config,
170
+ save_path="models/gnn_cond.pt",
171
+ )
172
+
173
+ # Load and predict one matrix or a list of matrices.
174
+ estimator = GNNConditionEstimator.load("models/gnn_cond.pt")
175
+ pred = estimator.predict(sp.random(100, 100, density=0.02, format="csr"))
176
+
177
+ # Scheme 2: inverse-norm prediction. At inference, sparse-kappa computes ||A||
178
+ # for the requested norm and returns ||A|| * predicted ||A^{-1}||.
179
+ inv_config = TrainingConfig(target="inverse_norm", norm=1, epochs=100)
180
+ inv_estimator = train_gnn_condition_estimator(train_samples, config=inv_config)
181
+ result = inv_estimator.predict(A_test, return_dict=True)
182
+ print(result["condition_number"], result["norm_A"], result["norm_Ainv"])
183
+ ```
184
+
185
+ Customization hooks follow the same shape:
186
+
187
+ ```python
188
+ import torch
189
+
190
+ estimator.fit(
191
+ train_samples,
192
+ val_data=val_samples,
193
+ optimizer_factory=lambda params: torch.optim.Adam(params, lr=5e-4),
194
+ scheduler_factory=lambda opt: torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=50),
195
+ loss_fn=torch.nn.SmoothL1Loss(),
196
+ validator=my_validation_callback,
197
+ )
198
+ ```
199
+
138
200
  ## Examples
139
201
 
140
202
  ### Example 1: Compare Methods
141
203
 
142
204
  ```python
143
- import cupyx.scipy.sparse as sp
205
+ from sparse_kappa.backend import sparse as sp
144
206
  from sparse_kappa import cond_estimate
145
207
 
146
208
  A = sp.random(2000, 2000, density=0.005, format='csr')
@@ -248,10 +310,13 @@ Contributions welcome! Please submit issues and pull requests on GitHub.
248
310
  If you use this library in your research, please cite:
249
311
 
250
312
  ```bibtex
251
- @software{sparse_kappa,
252
- title={Sparse Matrices Condition Number Estimation on GPUs},
253
- author={Xinye Chen},
254
- year={2026},
255
- url={https://github.com/chenxinye/sparse_kappa}
313
+ @misc{carson2026estimatingconditionnumbergraph,
314
+ title={Estimating condition number with Graph Neural Networks},
315
+ author={Erin Carson and Xinye Chen},
316
+ year={2026},
317
+ eprint={2603.10277},
318
+ archivePrefix={arXiv},
319
+ primaryClass={cs.LG},
320
+ url={https://arxiv.org/abs/2603.10277},
256
321
  }
257
322
  ```
@@ -2,28 +2,31 @@
2
2
 
3
3
  # sparse-kappa
4
4
 
5
- **Condition Number Estimation on GPUs for Sparse Matrices**
5
+ [![!pypi](https://img.shields.io/pypi/v/sparse-kappa?color=indigo)](https://pypi.org/project/sparse-kappa/)
6
+ [![License: MIT](https://anaconda.org/conda-forge/sparse-kappaclustering/badges/license.svg)]([https://github.com/nla-group/sparse-kappa/blob/master/LICENSE](https://opensource.org/licenses/MIT))
7
+ [![Documentation Status](https://readthedocs.org/projects/sparse-kappa/badge/?version=latest)](https://sparse-kappa.readthedocs.io/en/latest/)
6
8
 
7
9
 
8
-
9
- </div>
10
+ **Condition Number Estimation on CPUs/GPUs for Sparse Matrices**
10
11
 
11
12
 
12
- ``sparse-kappa`` is a GPU-accelerated library for estimating condition numbers of sparse matrices using CuPy. It supports a variaty of estimation method associated with linear solvers. ``sparse-kappa`` is designed for benchmarking condition number estimator and practical use in science and engineering community.
13
13
 
14
+ </div>
14
15
 
15
16
 
17
+ ``sparse-kappa`` is a GPU-accelerated library for estimating condition numbers of sparse matrices using PyTorch. It supports a variaty of estimation method associated with linear solvers. ``sparse-kappa`` is designed for benchmarking condition number estimator and practical use in science and engineering community.
16
18
 
17
19
 
18
20
  ## Features
19
21
 
20
- - **GPU-Accelerated**: All computations run on NVIDIA GPUs via CuPy
22
+ - **GPU-Accelerated**: All computations run on NVIDIA GPUs via PyTorch
21
23
  - **Multiple Norms**: Support for 1-norm and 2-norm condition numbers
22
24
  - **Rich Algorithm Suite**:
23
25
  - **1-norm**: Hager-Higham, Power iteration, Oettli-Prager sampling, Block Hager
24
- - **2-norm**: Power method, Lanczos, Arnoldi, Golub-Kahan bidiagonalization
25
- - **CuPy integrations**: SVDS, EIGSH, LOBPCG wrappers
26
+ - **2-norm**: Power method, Lanczos, Golub-Kahan bidiagonalization
27
+ - **PyTorch integrations**: SVDS, EIGSH, LOBPCG wrappers
26
28
  - **Flexible Solver System**: LU, LSMR, CG, GMRES, Direct, Auto-selection
29
+ - **GNN Prediction Module**: Train reusable models that predict condition numbers directly or via inverse-norm prediction
27
30
  - **Smart LU Caching**: Reuses factorizations for multiple solves (10-20x speedup)
28
31
  - **Memory Efficient**: Designed for large sparse matrices
29
32
 
@@ -38,14 +41,14 @@ pip install sparse-kappa
38
41
 
39
42
  ```bash
40
43
  git clone https://github.com/chenxinye/sparse-kappa
41
- pip install cupy-cuda11x # or cupy-cuda12x for CUDA 12
44
+ pip install torch
42
45
  pip install -e .
43
46
  ```
44
47
 
45
48
  ## Quick Start
46
49
 
47
50
  ```python
48
- import cupyx.scipy.sparse as sp
51
+ from sparse_kappa.backend import sparse as sp
49
52
  from sparse_kappa import cond_estimate
50
53
 
51
54
  # Create sparse matrix
@@ -74,16 +77,16 @@ cond = cond_estimate(A, norm=1, method='hager-higham', solver='lu')
74
77
 
75
78
  ## 2-Norm Methods
76
79
 
77
- | Method | Description | Best For | Complexity |
78
- |--------------|------------------------------------|------------------------------|-----------------|
79
- | `svds` | Partial SVD (most accurate) | Small-medium matrices (<5k) | O(k·nnz) |
80
- | `eigsh` | Symmetric eigenvalue solver | Symmetric matrices | O(k·nnz) |
81
- | `lobpcg` | Block preconditioned CG | Large matrices | O(k·nnz) |
82
- | `power` | Power iteration | Quick estimates | O(k·nnz) |
83
- | `lanczos` | Lanczos tridiagonalization | Medium matrices | O(k²·nnz) |
84
- | `arnoldi` | Arnoldi iteration | Non-symmetric | O(k²·nnz) |
85
- | `golub-kahan`| Bidiagonalization | Numerically stable | O(k·nnz) |
86
- | `auto` | Automatic selection | All cases | - |
80
+ | Method | Description | Best For | Complexity |
81
+ |-------------------|------------------------------------------------------------|-----------------------------------------|-----------------|
82
+ | `svds` | Partial SVD (most accurate) | Small-medium matrices (<5k) | O(k·nnz) |
83
+ | `eigsh` | Symmetric eigenvalue solver | Symmetric / Hermitian matrices | O(k·nnz) |
84
+ | `lobpcg` | Block preconditioned CG | Large matrices | O(k·nnz) |
85
+ | `power` | Power iteration | Quick estimates | O(k·nnz) |
86
+ | `lanczos` | Lanczos tridiagonalization | Medium symmetric matrices | O(k²·nnz) |
87
+ | `lanczos_unsym` | Lanczos-style condition estimation via `eigsh` on `A^H A` | Non-symmetric / rectangular matrices | O(k·nnz) |
88
+ | `golub-kahan` | Bidiagonalization | Numerically stable | O(k·nnz) |
89
+ | `auto` | Automatic selection | All cases | - |
87
90
 
88
91
  ## Solver Options
89
92
 
@@ -102,12 +105,70 @@ All 1-norm methods support flexible solver selection:
102
105
  **Legend:**
103
106
  `k` = iterations, `b` = block size, `m` = samples, `nnz` = non-zeros
104
107
 
108
+ ## GNN-Based Prediction
109
+
110
+ The `sparse_kappa.gnn` module learns a mapping from sparse matrices to
111
+ condition-number related scalars. It supports two training targets:
112
+
113
+ - `target="condition"`: predict `kappa(A)` directly.
114
+ - `target="inverse_norm"`: predict `||A^{-1}||`, then compute `||A|| * ||A^{-1}||` at inference time.
115
+
116
+ The default graph builder turns a sparse matrix into a row/column bipartite
117
+ graph. You can replace the feature extractor, model, optimizer, scheduler,
118
+ loss function, and validation callback.
119
+
120
+ ```python
121
+ from sparse_kappa import TrainingConfig, train_gnn_condition_estimator
122
+ from sparse_kappa.gnn import GNNConditionEstimator
123
+ from sparse_kappa.backend import sparse as sp
124
+
125
+ train_samples = [
126
+ {"matrix": A0, "condition_number": 12.3, "norm_Ainv": 0.42},
127
+ {"matrix": A1, "condition_number": 18.9, "norm_Ainv": 0.61},
128
+ ]
129
+
130
+ # Scheme 1: direct condition-number prediction
131
+ config = TrainingConfig(target="condition", norm=2, epochs=100, lr=1e-3)
132
+ estimator = train_gnn_condition_estimator(
133
+ train_samples,
134
+ val_data=None,
135
+ config=config,
136
+ save_path="models/gnn_cond.pt",
137
+ )
138
+
139
+ # Load and predict one matrix or a list of matrices.
140
+ estimator = GNNConditionEstimator.load("models/gnn_cond.pt")
141
+ pred = estimator.predict(sp.random(100, 100, density=0.02, format="csr"))
142
+
143
+ # Scheme 2: inverse-norm prediction. At inference, sparse-kappa computes ||A||
144
+ # for the requested norm and returns ||A|| * predicted ||A^{-1}||.
145
+ inv_config = TrainingConfig(target="inverse_norm", norm=1, epochs=100)
146
+ inv_estimator = train_gnn_condition_estimator(train_samples, config=inv_config)
147
+ result = inv_estimator.predict(A_test, return_dict=True)
148
+ print(result["condition_number"], result["norm_A"], result["norm_Ainv"])
149
+ ```
150
+
151
+ Customization hooks follow the same shape:
152
+
153
+ ```python
154
+ import torch
155
+
156
+ estimator.fit(
157
+ train_samples,
158
+ val_data=val_samples,
159
+ optimizer_factory=lambda params: torch.optim.Adam(params, lr=5e-4),
160
+ scheduler_factory=lambda opt: torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=50),
161
+ loss_fn=torch.nn.SmoothL1Loss(),
162
+ validator=my_validation_callback,
163
+ )
164
+ ```
165
+
105
166
  ## Examples
106
167
 
107
168
  ### Example 1: Compare Methods
108
169
 
109
170
  ```python
110
- import cupyx.scipy.sparse as sp
171
+ from sparse_kappa.backend import sparse as sp
111
172
  from sparse_kappa import cond_estimate
112
173
 
113
174
  A = sp.random(2000, 2000, density=0.005, format='csr')
@@ -215,10 +276,13 @@ Contributions welcome! Please submit issues and pull requests on GitHub.
215
276
  If you use this library in your research, please cite:
216
277
 
217
278
  ```bibtex
218
- @software{sparse_kappa,
219
- title={Sparse Matrices Condition Number Estimation on GPUs},
220
- author={Xinye Chen},
221
- year={2026},
222
- url={https://github.com/chenxinye/sparse_kappa}
279
+ @misc{carson2026estimatingconditionnumbergraph,
280
+ title={Estimating condition number with Graph Neural Networks},
281
+ author={Erin Carson and Xinye Chen},
282
+ year={2026},
283
+ eprint={2603.10277},
284
+ archivePrefix={arXiv},
285
+ primaryClass={cs.LG},
286
+ url={https://arxiv.org/abs/2603.10277},
223
287
  }
224
288
  ```
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "sparse-kappa"
7
- version = "0.0.2"
8
- description = "GPU-accelerated sparse matrix condition number estimation using CuPy"
7
+ version = "0.0.3"
8
+ description = "PyTorch-accelerated sparse matrix condition number estimation"
9
9
  readme = "README.md"
10
10
  authors = [
11
11
  {name = "Xinye Chen", email = "xinyechenai@gmail.com"}
@@ -24,8 +24,9 @@ classifiers = [
24
24
  "Programming Language :: Python :: 3.11",
25
25
  ]
26
26
  dependencies = [
27
- "cupy>=10.0.0",
28
27
  "numpy>=1.20.0",
28
+ "torch>=2.0.0",
29
+ "myst-parser>=2.0",
29
30
  ]
30
31
 
31
32
  [project.optional-dependencies]
@@ -37,4 +38,4 @@ dev = [
37
38
  ]
38
39
 
39
40
  [project.urls]
40
- Homepage = "https://github.com/chenxinye/sparse-kappa"
41
+ Homepage = "https://github.com/chenxinye/sparse-kappa"
@@ -5,10 +5,10 @@ with open("README.md", "r", encoding="utf-8") as fh:
5
5
 
6
6
  setup(
7
7
  name="sparse-kappa",
8
- version="0.0.1",
8
+ version="0.0.3",
9
9
  author="Xinye Chen",
10
10
  author_email="xinyechenai@gmail.com",
11
- description="GPU-accelerated sparse matrix condition number estimation using CuPy",
11
+ description="PyTorch-accelerated sparse matrix condition number estimation",
12
12
  long_description=long_description,
13
13
  long_description_content_type="text/markdown",
14
14
  url="https://github.com/chenxinye/sparse-kappa",
@@ -29,8 +29,9 @@ setup(
29
29
  ],
30
30
  python_requires=">=3.8",
31
31
  install_requires=[
32
- "cupy>=10.0.0",
32
+ "torch>=2.0.0",
33
33
  "numpy>=1.20.0",
34
+ "myst-parser>=2.0",
34
35
  ],
35
36
  extras_require={
36
37
  "dev": [
@@ -1,5 +1,5 @@
1
1
  """
2
- CuPy Sparse Condition Number Estimation Library
2
+ PyTorch Sparse Condition Number Estimation Library
3
3
 
4
4
  A comprehensive GPU-accelerated library for estimating condition numbers
5
5
  of sparse matrices using various iterative methods.
@@ -8,14 +8,18 @@ of sparse matrices using various iterative methods.
8
8
  from .sparse_kappa import cond_estimate, ConditionNumberEstimator
9
9
  from .solvers import create_solver, SOLVER_MAP
10
10
  from .config import configure_warnings
11
+ from .gnn import GNNConditionEstimator, TrainingConfig, train_gnn_condition_estimator
11
12
 
12
- __version__ = "0.0.2"
13
+ __version__ = "0.0.3"
13
14
  __all__ = [
14
15
  "cond_estimate",
15
16
  "ConditionNumberEstimator",
16
17
  "create_solver",
17
- "SOLVER_MAP"
18
+ "SOLVER_MAP",
19
+ "GNNConditionEstimator",
20
+ "TrainingConfig",
21
+ "train_gnn_condition_estimator",
18
22
  ]
19
23
 
20
24
 
21
- configure_warnings(convergence=False, format_conversion=False)
25
+ configure_warnings(convergence=False, format_conversion=False)
@@ -0,0 +1,6 @@
1
+ """PyTorch-backed compatibility layer used by sparse-kappa."""
2
+
3
+ from . import torch_api
4
+ from . import sparse
5
+
6
+ __all__ = ["torch_api", "sparse"]
@@ -0,0 +1,188 @@
1
+ """Sparse matrix compatibility layer backed by PyTorch tensors."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Any, Optional, Sequence, Tuple
7
+
8
+ import torch
9
+
10
+ from .. import torch_api as cp
11
+ from . import linalg
12
+
13
+
14
+ @dataclass
15
+ class TorchSparseMatrix:
16
+ _dense: torch.Tensor
17
+
18
+ def __post_init__(self) -> None:
19
+ if self._dense.ndim != 2:
20
+ raise ValueError("sparse matrices must be 2D")
21
+
22
+ @property
23
+ def shape(self) -> Tuple[int, int]:
24
+ return tuple(self._dense.shape)
25
+
26
+ @property
27
+ def dtype(self) -> torch.dtype:
28
+ return self._dense.dtype
29
+
30
+ @property
31
+ def nnz(self) -> int:
32
+ return int(torch.count_nonzero(self._dense).item())
33
+
34
+ @property
35
+ def data(self) -> torch.Tensor:
36
+ return self._dense[self._dense != 0]
37
+
38
+ @data.setter
39
+ def data(self, values: torch.Tensor) -> None:
40
+ mask = self._dense != 0
41
+ self._dense[mask] = values.to(dtype=self._dense.dtype, device=self._dense.device)
42
+
43
+ @property
44
+ def T(self) -> "TorchSparseMatrix":
45
+ return TorchSparseMatrix(self._dense.mT)
46
+
47
+ def conj(self) -> "TorchSparseMatrix":
48
+ return TorchSparseMatrix(torch.conj(self._dense))
49
+
50
+ def copy(self) -> "TorchSparseMatrix":
51
+ return TorchSparseMatrix(self._dense.clone())
52
+
53
+ def toarray(self) -> torch.Tensor:
54
+ return self._dense
55
+
56
+ def tocsr(self) -> "TorchSparseMatrix":
57
+ return self
58
+
59
+ def tocsc(self) -> "TorchSparseMatrix":
60
+ return self
61
+
62
+ def astype(self, dtype: Any) -> "TorchSparseMatrix":
63
+ return TorchSparseMatrix(self._dense.to(dtype=cp._normalize_dtype(dtype)))
64
+
65
+ def sum(self, axis: Optional[int] = None):
66
+ return self._dense.sum() if axis is None else self._dense.sum(dim=axis, keepdim=True)
67
+
68
+ def __getitem__(self, key: Any):
69
+ out = self._dense.__getitem__(key)
70
+ return TorchSparseMatrix(out) if isinstance(out, torch.Tensor) and out.ndim == 2 else out
71
+
72
+ def __matmul__(self, other: Any):
73
+ rhs = other._dense if isinstance(other, TorchSparseMatrix) else other
74
+ out = self._dense @ rhs
75
+ return TorchSparseMatrix(out) if isinstance(out, torch.Tensor) and out.ndim == 2 else out
76
+
77
+ def __rmatmul__(self, other: Any):
78
+ lhs = other._dense if isinstance(other, TorchSparseMatrix) else other
79
+ out = lhs @ self._dense
80
+ return TorchSparseMatrix(out) if isinstance(out, torch.Tensor) and out.ndim == 2 else out
81
+
82
+ def _binary(self, other: Any, op):
83
+ rhs = other._dense if isinstance(other, TorchSparseMatrix) else other
84
+ out = op(self._dense, rhs)
85
+ return TorchSparseMatrix(out) if isinstance(out, torch.Tensor) and out.ndim == 2 else out
86
+
87
+ def __add__(self, other: Any):
88
+ return self._binary(other, torch.add)
89
+
90
+ def __radd__(self, other: Any):
91
+ return self.__add__(other)
92
+
93
+ def __sub__(self, other: Any):
94
+ return self._binary(other, torch.sub)
95
+
96
+ def __rsub__(self, other: Any):
97
+ lhs = other._dense if isinstance(other, TorchSparseMatrix) else other
98
+ return TorchSparseMatrix(torch.sub(lhs, self._dense))
99
+
100
+ def __mul__(self, other: Any):
101
+ return self._binary(other, torch.mul)
102
+
103
+ def __rmul__(self, other: Any):
104
+ return self.__mul__(other)
105
+
106
+ def __truediv__(self, other: Any):
107
+ return self._binary(other, torch.true_divide)
108
+
109
+ def __repr__(self) -> str:
110
+ return f"TorchSparseMatrix(shape={self.shape}, nnz={self.nnz}, dtype={self.dtype})"
111
+
112
+
113
+ spmatrix = TorchSparseMatrix
114
+
115
+
116
+ def _dense_from_any(arg: Any, dtype: Any = None) -> torch.Tensor:
117
+ if isinstance(arg, TorchSparseMatrix):
118
+ dense = arg.toarray()
119
+ elif isinstance(arg, torch.Tensor):
120
+ dense = arg.to_dense() if arg.is_sparse else arg
121
+ elif hasattr(arg, "toarray"):
122
+ dense = cp.asarray(arg.toarray(), dtype=dtype)
123
+ else:
124
+ dense = cp.asarray(arg, dtype=dtype)
125
+ if dtype is not None:
126
+ dense = dense.to(dtype=cp._normalize_dtype(dtype))
127
+ return dense
128
+
129
+
130
+ def csr_matrix(arg: Any, shape: Optional[Tuple[int, int]] = None, dtype: Any = None) -> TorchSparseMatrix:
131
+ if isinstance(arg, tuple) and len(arg) == 2:
132
+ values, coords = arg
133
+ rows, cols = coords
134
+ values = cp.asarray(values, dtype=dtype)
135
+ rows = cp.asarray(rows, dtype=cp.int64)
136
+ cols = cp.asarray(cols, dtype=cp.int64)
137
+ if shape is None:
138
+ shape = (int(rows.max().item()) + 1, int(cols.max().item()) + 1)
139
+ dense = torch.zeros(shape, dtype=values.dtype, device=values.device)
140
+ dense[rows.long(), cols.long()] = values
141
+ return TorchSparseMatrix(dense)
142
+ dense = _dense_from_any(arg, dtype=dtype)
143
+ if shape is not None and tuple(dense.shape) != tuple(shape):
144
+ dense = dense.reshape(shape)
145
+ return TorchSparseMatrix(dense.clone())
146
+
147
+
148
+ def coo_matrix(arg: Any, shape: Optional[Tuple[int, int]] = None, dtype: Any = None) -> TorchSparseMatrix:
149
+ return csr_matrix(arg, shape=shape, dtype=dtype)
150
+
151
+
152
+ def isspmatrix(x: Any) -> bool:
153
+ return isinstance(x, TorchSparseMatrix)
154
+
155
+
156
+ def isspmatrix_csr(x: Any) -> bool:
157
+ return isinstance(x, TorchSparseMatrix)
158
+
159
+
160
+ def eye(n: int, m: Optional[int] = None, dtype: Any = cp.float64, format: str = "csr") -> TorchSparseMatrix:
161
+ return TorchSparseMatrix(cp.eye(n, m, dtype=dtype))
162
+
163
+
164
+ def identity(n: int, dtype: Any = cp.float64, format: str = "csr") -> TorchSparseMatrix:
165
+ return eye(n, dtype=dtype, format=format)
166
+
167
+
168
+ def diags(diagonals: Any, offsets: int = 0, shape: Optional[Tuple[int, int]] = None, format: str = "csr", dtype: Any = None) -> TorchSparseMatrix:
169
+ diag = cp.asarray(diagonals, dtype=dtype)
170
+ if offsets != 0:
171
+ raise NotImplementedError("Only the main diagonal is supported")
172
+ n = diag.numel()
173
+ if shape is None:
174
+ shape = (n, n)
175
+ dense = torch.zeros(shape, dtype=diag.dtype, device=diag.device)
176
+ idx = torch.arange(n, device=diag.device)
177
+ dense[idx, idx] = diag
178
+ return TorchSparseMatrix(dense)
179
+
180
+
181
+ def random(m: int, n: int, density: float = 0.01, format: str = "csr", dtype: Any = cp.float64, random_state: Optional[int] = None) -> TorchSparseMatrix:
182
+ dtype = cp._normalize_dtype(dtype)
183
+ generator = torch.Generator(device=cp._default_device())
184
+ if random_state is not None:
185
+ generator.manual_seed(random_state)
186
+ mask = torch.rand((m, n), generator=generator, device=cp._default_device()) < density
187
+ values = torch.rand((m, n), generator=generator, device=cp._default_device(), dtype=dtype)
188
+ return TorchSparseMatrix(torch.where(mask, values, torch.zeros((), dtype=dtype, device=values.device)))