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.
- {sparse_kappa-0.0.2/sparse_kappa.egg-info → sparse_kappa-0.0.3}/PKG-INFO +93 -28
- {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/README.md +89 -25
- {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/pyproject.toml +5 -4
- {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/setup.py +4 -3
- {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/sparse_kappa/__init__.py +8 -4
- sparse_kappa-0.0.3/sparse_kappa/backend/__init__.py +6 -0
- sparse_kappa-0.0.3/sparse_kappa/backend/sparse/__init__.py +188 -0
- sparse_kappa-0.0.3/sparse_kappa/backend/sparse/linalg.py +127 -0
- sparse_kappa-0.0.3/sparse_kappa/backend/torch_api.py +337 -0
- {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/sparse_kappa/config.py +1 -1
- sparse_kappa-0.0.3/sparse_kappa/gnn/__init__.py +20 -0
- sparse_kappa-0.0.3/sparse_kappa/gnn/data.py +108 -0
- sparse_kappa-0.0.3/sparse_kappa/gnn/features.py +161 -0
- sparse_kappa-0.0.3/sparse_kappa/gnn/models.py +113 -0
- sparse_kappa-0.0.3/sparse_kappa/gnn/training.py +323 -0
- {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/sparse_kappa/norm1/__init__.py +2 -1
- {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/sparse_kappa/norm1/hager.py +2 -2
- sparse_kappa-0.0.3/sparse_kappa/norm1/hager_higham.py +367 -0
- {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/sparse_kappa/norm1/inverse_norm.py +2 -2
- {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/sparse_kappa/norm1/monte_carlo.py +2 -2
- {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/sparse_kappa/norm1/oettli_prager.py +4 -4
- {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/sparse_kappa/norm1/power_iteration.py +2 -2
- {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/sparse_kappa/norm2/__init__.py +6 -3
- {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/sparse_kappa/norm2/golub_kahan.py +5 -5
- {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/sparse_kappa/norm2/lanczos.py +6 -6
- sparse_kappa-0.0.3/sparse_kappa/norm2/lanczos_unsym.py +171 -0
- sparse_kappa-0.0.3/sparse_kappa/norm2/power_method.py +422 -0
- sparse_kappa-0.0.3/sparse_kappa/norm2/svds.py +129 -0
- sparse_kappa-0.0.2/sparse_kappa/norm2/cupy_wrappers.py → sparse_kappa-0.0.3/sparse_kappa/norm2/torch_wrappers.py +12 -156
- sparse_kappa-0.0.3/sparse_kappa/notimplement.py +171 -0
- {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/sparse_kappa/solvers.py +29 -22
- {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/sparse_kappa/sparse_kappa.py +13 -12
- {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/sparse_kappa/utils.py +4 -4
- {sparse_kappa-0.0.2 → sparse_kappa-0.0.3/sparse_kappa.egg-info}/PKG-INFO +93 -28
- {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/sparse_kappa.egg-info/SOURCES.txt +27 -4
- {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/sparse_kappa.egg-info/requires.txt +2 -1
- {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/tests/test_1_norm_cond.py +5 -6
- {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/tests/test_accuracy.py +2 -2
- {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/tests/test_bicgstab_solver.py +2 -2
- sparse_kappa-0.0.3/tests/test_gnn.py +61 -0
- {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/tests/test_norm1.py +2 -2
- {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/tests/test_norm2.py +2 -2
- {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/tests/test_speed.py +8 -8
- sparse_kappa-0.0.2/sparse_kappa/norm1/hager_higham.py +0 -308
- sparse_kappa-0.0.2/sparse_kappa/norm2/arnoldi.py +0 -112
- sparse_kappa-0.0.2/sparse_kappa/norm2/power_method.py +0 -338
- {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/LICENSE +0 -0
- {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/setup.cfg +0 -0
- {sparse_kappa-0.0.2 → sparse_kappa-0.0.3}/sparse_kappa.egg-info/dependency_links.txt +0 -0
- {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.
|
|
4
|
-
Summary:
|
|
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
|
-
|
|
39
|
+
[](https://pypi.org/project/sparse-kappa/)
|
|
40
|
+
[]([https://github.com/nla-group/sparse-kappa/blob/master/LICENSE](https://opensource.org/licenses/MIT))
|
|
41
|
+
[](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
|
|
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,
|
|
58
|
-
- **
|
|
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
|
|
78
|
+
pip install torch
|
|
75
79
|
pip install -e .
|
|
76
80
|
```
|
|
77
81
|
|
|
78
82
|
## Quick Start
|
|
79
83
|
|
|
80
84
|
```python
|
|
81
|
-
import
|
|
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
|
|
111
|
-
|
|
112
|
-
| `svds`
|
|
113
|
-
| `eigsh`
|
|
114
|
-
| `lobpcg`
|
|
115
|
-
| `power`
|
|
116
|
-
| `lanczos`
|
|
117
|
-
| `
|
|
118
|
-
| `golub-kahan
|
|
119
|
-
| `auto`
|
|
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
|
|
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
|
-
@
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
-
|
|
5
|
+
[](https://pypi.org/project/sparse-kappa/)
|
|
6
|
+
[]([https://github.com/nla-group/sparse-kappa/blob/master/LICENSE](https://opensource.org/licenses/MIT))
|
|
7
|
+
[](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
|
|
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,
|
|
25
|
-
- **
|
|
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
|
|
44
|
+
pip install torch
|
|
42
45
|
pip install -e .
|
|
43
46
|
```
|
|
44
47
|
|
|
45
48
|
## Quick Start
|
|
46
49
|
|
|
47
50
|
```python
|
|
48
|
-
import
|
|
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
|
|
78
|
-
|
|
79
|
-
| `svds`
|
|
80
|
-
| `eigsh`
|
|
81
|
-
| `lobpcg`
|
|
82
|
-
| `power`
|
|
83
|
-
| `lanczos`
|
|
84
|
-
| `
|
|
85
|
-
| `golub-kahan
|
|
86
|
-
| `auto`
|
|
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
|
|
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
|
-
@
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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.
|
|
8
|
-
description = "
|
|
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.
|
|
8
|
+
version="0.0.3",
|
|
9
9
|
author="Xinye Chen",
|
|
10
10
|
author_email="xinyechenai@gmail.com",
|
|
11
|
-
description="
|
|
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
|
-
"
|
|
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
|
-
|
|
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.
|
|
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,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)))
|