solvax 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,19 @@
1
+ name: docs
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ build:
10
+ runs-on: ubuntu-latest
11
+ timeout-minutes: 10
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+ - uses: actions/setup-python@v5
15
+ with:
16
+ python-version: "3.12"
17
+ cache: pip
18
+ - run: pip install -e '.[docs]'
19
+ - run: python -m sphinx -W -b html docs docs/_build
@@ -0,0 +1,32 @@
1
+ name: publish
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+
7
+ jobs:
8
+ build:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v4
12
+ - uses: actions/setup-python@v5
13
+ with:
14
+ python-version: "3.12"
15
+ - run: pipx run build
16
+ - uses: actions/upload-artifact@v4
17
+ with:
18
+ name: dist
19
+ path: dist/
20
+
21
+ publish:
22
+ needs: build
23
+ runs-on: ubuntu-latest
24
+ environment: pypi
25
+ permissions:
26
+ id-token: write
27
+ steps:
28
+ - uses: actions/download-artifact@v4
29
+ with:
30
+ name: dist
31
+ path: dist/
32
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,30 @@
1
+ name: tests
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ concurrency:
9
+ group: ${{ github.workflow }}-${{ github.ref }}
10
+ cancel-in-progress: true
11
+
12
+ jobs:
13
+ test:
14
+ strategy:
15
+ matrix:
16
+ os: [ubuntu-latest, macos-latest]
17
+ runs-on: ${{ matrix.os }}
18
+ timeout-minutes: 15
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+ - uses: actions/setup-python@v5
22
+ with:
23
+ python-version: "3.12"
24
+ cache: pip
25
+ - run: pip install -e '.[dev]'
26
+ - run: pytest -n auto --cov --cov-report=xml
27
+ - uses: codecov/codecov-action@v4
28
+ if: matrix.os == 'ubuntu-latest'
29
+ with:
30
+ token: ${{ secrets.CODECOV_TOKEN }}
@@ -0,0 +1,9 @@
1
+ __pycache__/
2
+ *.egg-info/
3
+ .pytest_cache/
4
+ .coverage
5
+ coverage.xml
6
+ dist/
7
+ build/
8
+ docs/_build/
9
+ .DS_Store
@@ -0,0 +1,16 @@
1
+ version: 2
2
+
3
+ build:
4
+ os: ubuntu-24.04
5
+ tools:
6
+ python: "3.12"
7
+
8
+ sphinx:
9
+ configuration: docs/conf.py
10
+ fail_on_warning: true
11
+
12
+ python:
13
+ install:
14
+ - method: pip
15
+ path: .
16
+ extra_requirements: [docs]
@@ -0,0 +1,10 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use solvax in your research, please cite it as below."
3
+ title: "solvax: differentiable structured linear solvers in JAX"
4
+ authors:
5
+ - family-names: Jorge
6
+ given-names: Rogerio
7
+ email: rogerio.jorge@wisc.edu
8
+ affiliation: University of Wisconsin-Madison
9
+ repository-code: "https://github.com/uwplasma/SOLVAX"
10
+ license: MIT
solvax-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 UW Plasma group, University of Wisconsin-Madison
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
solvax-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,115 @@
1
+ Metadata-Version: 2.4
2
+ Name: solvax
3
+ Version: 0.1.0
4
+ Summary: Differentiable structured linear solvers, preconditioners and matrix-free methods in JAX
5
+ Project-URL: Homepage, https://github.com/uwplasma/SOLVAX
6
+ Project-URL: Documentation, https://solvax.readthedocs.io
7
+ Author-email: UW Plasma <rogerio.jorge@wisc.edu>
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
15
+ Classifier: Topic :: Scientific/Engineering :: Physics
16
+ Requires-Python: >=3.10
17
+ Requires-Dist: equinox
18
+ Requires-Dist: jax
19
+ Requires-Dist: lineax
20
+ Provides-Extra: dev
21
+ Requires-Dist: numpy; extra == 'dev'
22
+ Requires-Dist: pytest; extra == 'dev'
23
+ Requires-Dist: pytest-cov; extra == 'dev'
24
+ Requires-Dist: pytest-xdist; extra == 'dev'
25
+ Requires-Dist: scipy; extra == 'dev'
26
+ Provides-Extra: docs
27
+ Requires-Dist: furo; extra == 'docs'
28
+ Requires-Dist: myst-parser; extra == 'docs'
29
+ Requires-Dist: sphinx; extra == 'docs'
30
+ Requires-Dist: sphinx-copybutton; extra == 'docs'
31
+ Requires-Dist: sphinxcontrib-bibtex; extra == 'docs'
32
+ Provides-Extra: native
33
+ Requires-Dist: scipy; extra == 'native'
34
+ Description-Content-Type: text/markdown
35
+
36
+ # SOLVAX
37
+
38
+ [![tests](https://github.com/uwplasma/SOLVAX/actions/workflows/tests.yml/badge.svg)](https://github.com/uwplasma/SOLVAX/actions/workflows/tests.yml)
39
+ [![codecov](https://codecov.io/gh/uwplasma/SOLVAX/branch/main/graph/badge.svg)](https://codecov.io/gh/uwplasma/SOLVAX)
40
+ [![PyPI](https://img.shields.io/pypi/v/solvax)](https://pypi.org/project/solvax/)
41
+ [![docs](https://readthedocs.org/projects/solvax/badge/?version=latest)](https://solvax.readthedocs.io)
42
+ [![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
43
+
44
+ **Differentiable structured linear solvers, preconditioners and matrix-free methods in JAX.**
45
+
46
+ `solvax` provides the solver infrastructure that kinetic and PDE codes keep
47
+ re-implementing: structured direct solves (batched dense LU, block-tridiagonal
48
+ Schur elimination with truncated storage), preconditioned and recycled Krylov
49
+ methods, physics-agnostic preconditioners (coarse-operator LU, p-multigrid,
50
+ Kronecker approximations, line smoothers), mixed-precision iterative
51
+ refinement, and implicit differentiation of every solve — all
52
+ jit/vmap/grad-transparent, on CPU and GPU.
53
+
54
+ It fills a gap in the JAX ecosystem: [lineax](https://github.com/patrick-kidger/lineax)
55
+ offers general linear-operator abstractions and standard solvers, but not
56
+ block-structured direct elimination, coarse-operator/multigrid
57
+ preconditioning, or Krylov subspace recycling for parameter continuation.
58
+ `solvax` builds on lineax's operator interface and adds exactly that layer.
59
+
60
+ ## Install
61
+
62
+ ```bash
63
+ pip install solvax
64
+ ```
65
+
66
+ ## Quickstart
67
+
68
+ ```python
69
+ import jax.numpy as jnp
70
+ import solvax as sx
71
+
72
+ # Solve a block-tridiagonal system L_k x_{k-1} + D_k x_k + U_k x_{k+1} = b_k
73
+ x = sx.block_thomas(lower, diag, upper, rhs)
74
+
75
+ # Reuse one elimination across many right-hand sides
76
+ factors = sx.block_thomas_factor(lower, diag, upper)
77
+ x1 = sx.block_thomas_solve(factors, rhs1)
78
+ x2 = sx.block_thomas_solve(factors, rhs2)
79
+
80
+ # Memory-truncated mode: rhs nonzero only in the lowest K blocks and only the
81
+ # lowest K solution blocks needed -> O(K m^2) memory, independent of N.
82
+ x_low = sx.block_thomas_truncated(lower, diag, upper, rhs[:3], keep_lowest=3)
83
+ ```
84
+
85
+ Everything is differentiable (`jax.grad` through the solve) and batchable
86
+ (`jax.vmap` over stacked systems).
87
+
88
+ ## What's in the box
89
+
90
+ | Module | Contents |
91
+ |---|---|
92
+ | `solvax.operators` | Matrix-free, sum, Kronecker, block-tridiagonal and bordered (constraint-row) operator containers with closed-form transposes |
93
+ | `solvax.precond` | Jacobi/block-Jacobi, coarse-operator LU, alternating-direction line smoothers, p-multigrid V-cycles, nearest-Kronecker, mixed-precision wrappers |
94
+ | `solvax.direct` | Block-tridiagonal Schur elimination (block Thomas): full, factor/solve split, truncated-storage mode |
95
+ | `solvax.banded` | Non-pivoted banded LU with row equilibration + static pivoting; periodic variant via the Woodbury capacitance trick |
96
+ | `solvax.krylov` | Flexible restarted GMRES (CGS2 + Givens) and GCROT-style Krylov subspace recycling for parameter continuation |
97
+ | `solvax.implicit` | Implicit-function-theorem `linear_solve` and `root_solve` — gradients cost one extra (transposed) solve |
98
+ | `solvax.refine` | Mixed-precision iterative refinement (float32 factor, float64 residuals) |
99
+ | `solvax.native` | Host-side SuperLU bridge (non-differentiable, import-guarded) |
100
+
101
+ Roadmap to v0.2: harmonic-Ritz recycle selection, pytree operands, complex
102
+ dtypes, GPU batched-LU benchmarks.
103
+
104
+ ```python
105
+ # Preconditioned, recycled Krylov across a parameter scan:
106
+ sol = sx.gcrot(matvec, b, precond=coarse_inverse, m=50, k=10)
107
+ sol2 = sx.gcrot(matvec2, b2, precond=coarse_inverse, recycle=sol.recycle)
108
+
109
+ # Differentiable solve wrapping any solver:
110
+ x = sx.linear_solve(matvec, b, solver=lambda mv, rhs: sx.gmres(mv, rhs).x)
111
+ ```
112
+
113
+ ## License
114
+
115
+ MIT. Developed by the [UW Plasma group](https://github.com/uwplasma).
solvax-0.1.0/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # SOLVAX
2
+
3
+ [![tests](https://github.com/uwplasma/SOLVAX/actions/workflows/tests.yml/badge.svg)](https://github.com/uwplasma/SOLVAX/actions/workflows/tests.yml)
4
+ [![codecov](https://codecov.io/gh/uwplasma/SOLVAX/branch/main/graph/badge.svg)](https://codecov.io/gh/uwplasma/SOLVAX)
5
+ [![PyPI](https://img.shields.io/pypi/v/solvax)](https://pypi.org/project/solvax/)
6
+ [![docs](https://readthedocs.org/projects/solvax/badge/?version=latest)](https://solvax.readthedocs.io)
7
+ [![license](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
8
+
9
+ **Differentiable structured linear solvers, preconditioners and matrix-free methods in JAX.**
10
+
11
+ `solvax` provides the solver infrastructure that kinetic and PDE codes keep
12
+ re-implementing: structured direct solves (batched dense LU, block-tridiagonal
13
+ Schur elimination with truncated storage), preconditioned and recycled Krylov
14
+ methods, physics-agnostic preconditioners (coarse-operator LU, p-multigrid,
15
+ Kronecker approximations, line smoothers), mixed-precision iterative
16
+ refinement, and implicit differentiation of every solve — all
17
+ jit/vmap/grad-transparent, on CPU and GPU.
18
+
19
+ It fills a gap in the JAX ecosystem: [lineax](https://github.com/patrick-kidger/lineax)
20
+ offers general linear-operator abstractions and standard solvers, but not
21
+ block-structured direct elimination, coarse-operator/multigrid
22
+ preconditioning, or Krylov subspace recycling for parameter continuation.
23
+ `solvax` builds on lineax's operator interface and adds exactly that layer.
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ pip install solvax
29
+ ```
30
+
31
+ ## Quickstart
32
+
33
+ ```python
34
+ import jax.numpy as jnp
35
+ import solvax as sx
36
+
37
+ # Solve a block-tridiagonal system L_k x_{k-1} + D_k x_k + U_k x_{k+1} = b_k
38
+ x = sx.block_thomas(lower, diag, upper, rhs)
39
+
40
+ # Reuse one elimination across many right-hand sides
41
+ factors = sx.block_thomas_factor(lower, diag, upper)
42
+ x1 = sx.block_thomas_solve(factors, rhs1)
43
+ x2 = sx.block_thomas_solve(factors, rhs2)
44
+
45
+ # Memory-truncated mode: rhs nonzero only in the lowest K blocks and only the
46
+ # lowest K solution blocks needed -> O(K m^2) memory, independent of N.
47
+ x_low = sx.block_thomas_truncated(lower, diag, upper, rhs[:3], keep_lowest=3)
48
+ ```
49
+
50
+ Everything is differentiable (`jax.grad` through the solve) and batchable
51
+ (`jax.vmap` over stacked systems).
52
+
53
+ ## What's in the box
54
+
55
+ | Module | Contents |
56
+ |---|---|
57
+ | `solvax.operators` | Matrix-free, sum, Kronecker, block-tridiagonal and bordered (constraint-row) operator containers with closed-form transposes |
58
+ | `solvax.precond` | Jacobi/block-Jacobi, coarse-operator LU, alternating-direction line smoothers, p-multigrid V-cycles, nearest-Kronecker, mixed-precision wrappers |
59
+ | `solvax.direct` | Block-tridiagonal Schur elimination (block Thomas): full, factor/solve split, truncated-storage mode |
60
+ | `solvax.banded` | Non-pivoted banded LU with row equilibration + static pivoting; periodic variant via the Woodbury capacitance trick |
61
+ | `solvax.krylov` | Flexible restarted GMRES (CGS2 + Givens) and GCROT-style Krylov subspace recycling for parameter continuation |
62
+ | `solvax.implicit` | Implicit-function-theorem `linear_solve` and `root_solve` — gradients cost one extra (transposed) solve |
63
+ | `solvax.refine` | Mixed-precision iterative refinement (float32 factor, float64 residuals) |
64
+ | `solvax.native` | Host-side SuperLU bridge (non-differentiable, import-guarded) |
65
+
66
+ Roadmap to v0.2: harmonic-Ritz recycle selection, pytree operands, complex
67
+ dtypes, GPU batched-LU benchmarks.
68
+
69
+ ```python
70
+ # Preconditioned, recycled Krylov across a parameter scan:
71
+ sol = sx.gcrot(matvec, b, precond=coarse_inverse, m=50, k=10)
72
+ sol2 = sx.gcrot(matvec2, b2, precond=coarse_inverse, recycle=sol.recycle)
73
+
74
+ # Differentiable solve wrapping any solver:
75
+ x = sx.linear_solve(matvec, b, solver=lambda mv, rhs: sx.gmres(mv, rhs).x)
76
+ ```
77
+
78
+ ## License
79
+
80
+ MIT. Developed by the [UW Plasma group](https://github.com/uwplasma).
@@ -0,0 +1,11 @@
1
+ coverage:
2
+ status:
3
+ project:
4
+ default:
5
+ target: 95%
6
+ threshold: 1%
7
+ patch:
8
+ default:
9
+ target: 90%
10
+ comment:
11
+ layout: "condensed_header, diff"
@@ -0,0 +1,65 @@
1
+ # API reference
2
+
3
+ ## Linear operators
4
+
5
+ ```{eval-rst}
6
+ .. automodule:: solvax.operators
7
+ :members:
8
+ :member-order: bysource
9
+ ```
10
+
11
+ ## Preconditioners
12
+
13
+ ```{eval-rst}
14
+ .. automodule:: solvax.precond
15
+ :members:
16
+ :member-order: bysource
17
+ ```
18
+
19
+ ## Structured direct solvers
20
+
21
+ ```{eval-rst}
22
+ .. automodule:: solvax.direct
23
+ :members:
24
+ :member-order: bysource
25
+ ```
26
+
27
+ ## Banded and periodic-banded LU
28
+
29
+ ```{eval-rst}
30
+ .. automodule:: solvax.banded
31
+ :members:
32
+ :member-order: bysource
33
+ ```
34
+
35
+ ## Krylov methods
36
+
37
+ ```{eval-rst}
38
+ .. automodule:: solvax.krylov
39
+ :members:
40
+ :member-order: bysource
41
+ ```
42
+
43
+ ## Implicit differentiation
44
+
45
+ ```{eval-rst}
46
+ .. automodule:: solvax.implicit
47
+ :members:
48
+ :member-order: bysource
49
+ ```
50
+
51
+ ## Mixed-precision refinement
52
+
53
+ ```{eval-rst}
54
+ .. automodule:: solvax.refine
55
+ :members:
56
+ :member-order: bysource
57
+ ```
58
+
59
+ ## Native host bridges
60
+
61
+ ```{eval-rst}
62
+ .. automodule:: solvax.native
63
+ :members:
64
+ :member-order: bysource
65
+ ```
@@ -0,0 +1,35 @@
1
+ """Sphinx configuration for the solvax documentation."""
2
+
3
+ import solvax
4
+
5
+ project = "solvax"
6
+ copyright = "2026, UW Plasma group"
7
+ author = "UW Plasma group"
8
+ release = solvax.__version__
9
+
10
+ extensions = [
11
+ "sphinx.ext.autodoc",
12
+ "sphinx.ext.napoleon",
13
+ "sphinx.ext.mathjax",
14
+ "sphinx.ext.intersphinx",
15
+ "myst_parser",
16
+ "sphinx_copybutton",
17
+ "sphinxcontrib.bibtex",
18
+ ]
19
+
20
+ bibtex_bibfiles = ["references.bib"]
21
+ myst_enable_extensions = ["dollarmath", "amsmath"]
22
+
23
+ html_theme = "furo"
24
+ html_title = "solvax"
25
+
26
+ intersphinx_mapping = {
27
+ "jax": ("https://docs.jax.dev/en/latest/", None),
28
+ "python": ("https://docs.python.org/3", None),
29
+ }
30
+
31
+ autodoc_typehints = "description"
32
+ napoleon_google_docstring = True
33
+ # NamedTuple fields are already documented via napoleon Attributes sections;
34
+ # ivar rendering avoids duplicate autodoc entries for the same objects.
35
+ napoleon_use_ivar = True
@@ -0,0 +1,51 @@
1
+ # solvax
2
+
3
+ **Differentiable structured linear solvers, preconditioners and matrix-free
4
+ methods in JAX.**
5
+
6
+ `solvax` is the solver layer that kinetic and PDE codes keep re-implementing,
7
+ factored out once: structured direct solves, preconditioned and recycled
8
+ Krylov methods, physics-agnostic preconditioners, mixed-precision refinement,
9
+ and implicit differentiation — all jit/vmap/grad-transparent on CPU and GPU.
10
+
11
+ ## Quickstart
12
+
13
+ ```python
14
+ import solvax as sx
15
+
16
+ # Block-tridiagonal system: L_k x_{k-1} + D_k x_k + U_k x_{k+1} = b_k
17
+ x = sx.block_thomas(lower, diag, upper, rhs)
18
+
19
+ # Reuse one elimination across right-hand sides
20
+ factors = sx.block_thomas_factor(lower, diag, upper)
21
+ x1 = sx.block_thomas_solve(factors, rhs1)
22
+ x2 = sx.block_thomas_solve(factors, rhs2)
23
+ ```
24
+
25
+ ## The block-tridiagonal kernel
26
+
27
+ For blocks $L_k x_{k-1} + D_k x_k + U_k x_{k+1} = b_k$, a Schur-complement
28
+ sweep from the last block down,
29
+
30
+ $$\Delta_{N-1} = D_{N-1}, \qquad
31
+ \Delta_k = D_k - U_k \Delta_{k+1}^{-1} L_{k+1}, \qquad
32
+ \sigma_k = b_k - U_k \Delta_{k+1}^{-1} \sigma_{k+1},$$
33
+
34
+ followed by substitution upward from block 0, solves the system exactly with
35
+ one dense LU and one matrix product per block {cite}`golub2013,demmel1995`.
36
+ When the right-hand side vanishes above block $K$ and only the lowest $K$
37
+ solution blocks are needed — the typical situation for spectral kinetic
38
+ equations, where sources and velocity moments involve only the first few
39
+ modes — storage above $K$ can be discarded on the fly, giving memory
40
+ $O(K m^2)$ independent of $N$ {cite}`escoto2025`.
41
+
42
+ ```{toctree}
43
+ :maxdepth: 1
44
+
45
+ api
46
+ ```
47
+
48
+ ## References
49
+
50
+ ```{bibliography}
51
+ ```
@@ -0,0 +1,85 @@
1
+ @book{golub2013,
2
+ author = {Golub, Gene H. and Van Loan, Charles F.},
3
+ title = {Matrix Computations},
4
+ edition = {4},
5
+ publisher = {Johns Hopkins University Press},
6
+ year = {2013},
7
+ }
8
+
9
+ @article{demmel1995,
10
+ author = {Demmel, James W. and Higham, Nicholas J. and Schreiber, Robert S.},
11
+ title = {Stability of block {LU} factorization},
12
+ journal = {Numerical Linear Algebra with Applications},
13
+ volume = {2},
14
+ pages = {173--190},
15
+ year = {1995},
16
+ doi = {10.1002/nla.1680020208},
17
+ }
18
+
19
+ @phdthesis{escoto2025,
20
+ author = {Escoto, F. Javier},
21
+ title = {Fast and accurate calculation of the bootstrap current and radial
22
+ neoclassical transport in low collisionality stellarator plasmas},
23
+ school = {Universidad Carlos III de Madrid},
24
+ year = {2025},
25
+ url = {https://arxiv.org/abs/2510.27513},
26
+ }
27
+
28
+ @article{morgan2002,
29
+ author = {Morgan, Ronald B.},
30
+ title = {{GMRES} with deflated restarting},
31
+ journal = {SIAM Journal on Scientific Computing},
32
+ volume = {24},
33
+ pages = {20--37},
34
+ year = {2002},
35
+ doi = {10.1137/S1064827599364659},
36
+ }
37
+
38
+ @article{parks2006,
39
+ author = {Parks, Michael L. and de Sturler, Eric and Mackey, Greg and
40
+ Johnson, Duane D. and Maiti, Spandan},
41
+ title = {Recycling {Krylov} subspaces for sequences of linear systems},
42
+ journal = {SIAM Journal on Scientific Computing},
43
+ volume = {28},
44
+ pages = {1651--1674},
45
+ year = {2006},
46
+ doi = {10.1137/040607277},
47
+ }
48
+
49
+ @article{carson2018,
50
+ author = {Carson, Erin and Higham, Nicholas J.},
51
+ title = {Accelerating the solution of linear systems by iterative
52
+ refinement in three precisions},
53
+ journal = {SIAM Journal on Scientific Computing},
54
+ volume = {40},
55
+ pages = {A817--A847},
56
+ year = {2018},
57
+ doi = {10.1137/17M1140819},
58
+ }
59
+
60
+ @inproceedings{blondel2022,
61
+ author = {Blondel, Mathieu and Berthet, Quentin and Cuturi, Marco and
62
+ Frostig, Roy and Hoyer, Stephan and Llinares-L{\'o}pez, Felipe
63
+ and Pedregosa, Fabian and Vert, Jean-Philippe},
64
+ title = {Efficient and modular implicit differentiation},
65
+ booktitle = {Advances in Neural Information Processing Systems},
66
+ year = {2022},
67
+ url = {https://arxiv.org/abs/2105.15183},
68
+ }
69
+
70
+ @article{skene2026,
71
+ author = {Skene, Calum S. and Burns, Keaton J.},
72
+ title = {Fast automated adjoints for spectral {PDE} solvers},
73
+ journal = {Proceedings of the National Academy of Sciences},
74
+ year = {2026},
75
+ url = {https://arxiv.org/abs/2506.14792},
76
+ }
77
+
78
+ @incollection{vanloan1993,
79
+ author = {Van Loan, Charles F. and Pitsianis, Nikos},
80
+ title = {Approximation with {Kronecker} products},
81
+ booktitle = {Linear Algebra for Large Scale and Real-Time Applications},
82
+ publisher = {Springer},
83
+ year = {1993},
84
+ doi = {10.1007/978-94-015-8196-7_17},
85
+ }
@@ -0,0 +1,49 @@
1
+ """Solve a kinetic-style block-tridiagonal system with truncated storage.
2
+
3
+ Spectral discretizations of kinetic equations (e.g. a Legendre expansion in
4
+ pitch angle) couple only neighbouring modes l-1, l, l+1, giving a
5
+ block-tridiagonal system whose right-hand side lives in the lowest few modes
6
+ and whose observables (density, flow, pressure moments) touch only those same
7
+ modes. `block_thomas_truncated` exploits both facts: memory O(K m^2)
8
+ independent of the number of modes.
9
+
10
+ Expected runtime: a few seconds on a laptop CPU.
11
+ """
12
+
13
+ import jax
14
+ import jax.numpy as jnp
15
+
16
+ import solvax as sx
17
+
18
+ jax.config.update("jax_enable_x64", True)
19
+
20
+ n_modes, m = 64, 100 # Legendre-like modes x flux-surface grid points
21
+ key = jax.random.PRNGKey(0)
22
+ k1, k2, k3, k4 = jax.random.split(key, 4)
23
+
24
+ # Streaming-like off-diagonal coupling and a collisional diagonal ~ l(l+1).
25
+ lower = 0.3 * jax.random.normal(k1, (n_modes, m, m))
26
+ upper = 0.3 * jax.random.normal(k2, (n_modes, m, m))
27
+ nu = 0.5 * jnp.arange(n_modes) * (jnp.arange(n_modes) + 1) + 5.0
28
+ diag = jax.random.normal(k3, (n_modes, m, m)) + nu[:, None, None] * jnp.eye(m)
29
+
30
+ # Two drives (radial + parallel), nonzero only in modes 0..2 — solved together.
31
+ rhs_low = jax.random.normal(k4, (3, m, 2))
32
+
33
+ x_low = sx.block_thomas_truncated(lower, diag, upper, rhs_low, keep_lowest=3)
34
+ print("lowest-mode solution block shape:", x_low.shape)
35
+
36
+ # Cross-check against the full solve.
37
+ rhs_full = jnp.zeros((n_modes, m, 2)).at[:3].set(rhs_low)
38
+ x_full = sx.block_thomas(lower, diag, upper, rhs_full)
39
+ err = jnp.max(jnp.abs(x_low - x_full[:3]))
40
+ print(f"max |truncated - full| = {err:.2e}")
41
+
42
+ # The whole solve is differentiable: gradient of a "flux" moment w.r.t. nu.
43
+ def flux(nu_vec):
44
+ d = jax.random.normal(k3, (n_modes, m, m)) + nu_vec[:, None, None] * jnp.eye(m)
45
+ x = sx.block_thomas_truncated(lower, d, upper, rhs_low, keep_lowest=3)
46
+ return jnp.sum(x[1] ** 2) # mode-1 moment ~ parallel flow
47
+
48
+ g = jax.grad(flux)(nu)
49
+ print("d(flux)/d(nu_0) =", float(g[0]))