FastLSQ 0.1.4__tar.gz → 0.2.1__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 (132) hide show
  1. {fastlsq-0.1.4 → fastlsq-0.2.1}/CHANGELOG.md +81 -2
  2. {fastlsq-0.1.4 → fastlsq-0.2.1/FastLSQ.egg-info}/PKG-INFO +86 -4
  3. fastlsq-0.2.1/FastLSQ.egg-info/SOURCES.txt +116 -0
  4. {fastlsq-0.1.4 → fastlsq-0.2.1}/FastLSQ.egg-info/requires.txt +5 -0
  5. {fastlsq-0.1.4/FastLSQ.egg-info → fastlsq-0.2.1}/PKG-INFO +86 -4
  6. {fastlsq-0.1.4 → fastlsq-0.2.1}/README.md +78 -1
  7. fastlsq-0.2.1/examples/extras/fred_sde.py +262 -0
  8. fastlsq-0.2.1/examples/extras/fred_sde_fastlsq.py +329 -0
  9. fastlsq-0.2.1/examples/extras/gaia_potential.py +183 -0
  10. fastlsq-0.2.1/examples/extras/gaia_potential_fastlsq.py +397 -0
  11. fastlsq-0.2.1/examples/extras/horizons_ephemeris.py +319 -0
  12. fastlsq-0.2.1/examples/extras/numerai_alpha.py +214 -0
  13. fastlsq-0.2.1/examples/extras/numerai_alpha_fastlsq.py +374 -0
  14. fastlsq-0.2.1/examples/extras/run_all_fastlsq.py +107 -0
  15. fastlsq-0.2.1/examples/extras/scenarios/__init__.py +0 -0
  16. fastlsq-0.2.1/examples/extras/scenarios/_alsu_lattice.py +196 -0
  17. fastlsq-0.2.1/examples/extras/scenarios/_common.py +164 -0
  18. fastlsq-0.2.1/examples/extras/scenarios/run_all.py +70 -0
  19. fastlsq-0.2.1/examples/extras/scenarios/s01_beamloss_ode.py +340 -0
  20. fastlsq-0.2.1/examples/extras/scenarios/s01_betatron_tune.py +448 -0
  21. fastlsq-0.2.1/examples/extras/scenarios/s01_green_fff.py +262 -0
  22. fastlsq-0.2.1/examples/extras/scenarios/s01_hill_ivp.py +263 -0
  23. fastlsq-0.2.1/examples/extras/scenarios/s01_observe_fit_act_simulator.py +353 -0
  24. fastlsq-0.2.1/examples/extras/scenarios/s01_orbit_inverse.py +332 -0
  25. fastlsq-0.2.1/examples/extras/scenarios/s01_passive_loco.py +246 -0
  26. fastlsq-0.2.1/examples/extras/scenarios/s01_perturbed_hill.py +247 -0
  27. fastlsq-0.2.1/examples/extras/scenarios/s01_sofb_observe_fit_act.py +619 -0
  28. fastlsq-0.2.1/examples/extras/scenarios/s01_streaming_archive_growth.py +310 -0
  29. fastlsq-0.2.1/examples/extras/scenarios/s01_synchrotron_ode.py +335 -0
  30. fastlsq-0.2.1/examples/extras/scenarios/s01_tides_3months.py +257 -0
  31. fastlsq-0.2.1/examples/extras/scenarios/s01_topoff_impulse.py +375 -0
  32. fastlsq-0.2.1/examples/extras/scenarios/s01_visualize.py +400 -0
  33. fastlsq-0.2.1/examples/extras/scenarios/s02_plasma_wakefield.py +99 -0
  34. fastlsq-0.2.1/examples/extras/scenarios/s03_synchrobetatron.py +75 -0
  35. fastlsq-0.2.1/examples/extras/scenarios/s04_sunspots.py +116 -0
  36. fastlsq-0.2.1/examples/extras/scenarios/s05_helioseismology.py +105 -0
  37. fastlsq-0.2.1/examples/extras/scenarios/s06_tides.py +122 -0
  38. fastlsq-0.2.1/examples/extras/scenarios/s07_iers_earth_rotation.py +97 -0
  39. fastlsq-0.2.1/examples/extras/scenarios/s08_mauna_loa_co2.py +96 -0
  40. fastlsq-0.2.1/examples/extras/scenarios/s09_enso_qbo.py +131 -0
  41. fastlsq-0.2.1/examples/extras/scenarios/s10_pulsar_timing.py +99 -0
  42. fastlsq-0.2.1/examples/extras/scenarios/s11_modal_analysis.py +109 -0
  43. fastlsq-0.2.1/examples/extras/scenarios/s12_mems_resonator.py +150 -0
  44. fastlsq-0.2.1/examples/extras/scenarios/s13_variable_stars_kepler.py +111 -0
  45. fastlsq-0.2.1/examples/extras/scenarios/s14_eeg.py +94 -0
  46. fastlsq-0.2.1/examples/extras/scenarios/s15_circadian.py +130 -0
  47. fastlsq-0.2.1/examples/extras/spectral_expansion.py +96 -0
  48. fastlsq-0.2.1/examples/grad_shafranov.py +186 -0
  49. fastlsq-0.2.1/examples/grid_inverse.py +74 -0
  50. fastlsq-0.2.1/examples/grid_rl_control.py +128 -0
  51. fastlsq-0.2.1/examples/grid_swing.py +130 -0
  52. fastlsq-0.2.1/examples/gs_inverse.py +127 -0
  53. fastlsq-0.2.1/examples/gs_rl_control.py +182 -0
  54. fastlsq-0.2.1/examples/orbit_hill.py +210 -0
  55. fastlsq-0.2.1/examples/orbit_inverse.py +98 -0
  56. fastlsq-0.2.1/examples/orbit_rl.py +154 -0
  57. fastlsq-0.2.1/examples/vector_basis_stream_vorticity.py +146 -0
  58. {fastlsq-0.1.4 → fastlsq-0.2.1}/fastlsq/__init__.py +17 -1
  59. {fastlsq-0.1.4 → fastlsq-0.2.1}/fastlsq/api.py +18 -7
  60. {fastlsq-0.1.4 → fastlsq-0.2.1}/fastlsq/basis.py +46 -9
  61. fastlsq-0.2.1/fastlsq/block.py +105 -0
  62. fastlsq-0.2.1/fastlsq/device.py +130 -0
  63. {fastlsq-0.1.4 → fastlsq-0.2.1}/fastlsq/geometry.py +16 -16
  64. {fastlsq-0.1.4 → fastlsq-0.2.1}/fastlsq/learnable.py +124 -57
  65. fastlsq-0.2.1/fastlsq/linalg.py +137 -0
  66. {fastlsq-0.1.4 → fastlsq-0.2.1}/fastlsq/newton.py +23 -7
  67. {fastlsq-0.1.4 → fastlsq-0.2.1}/fastlsq/problems/__init__.py +6 -4
  68. {fastlsq-0.1.4 → fastlsq-0.2.1}/fastlsq/solvers.py +39 -13
  69. {fastlsq-0.1.4 → fastlsq-0.2.1}/fastlsq/tuning.py +9 -3
  70. {fastlsq-0.1.4 → fastlsq-0.2.1}/fastlsq/utils.py +8 -2
  71. fastlsq-0.2.1/fastlsq/vector.py +412 -0
  72. fastlsq-0.2.1/fastlsq/viz.py +456 -0
  73. {fastlsq-0.1.4 → fastlsq-0.2.1}/misc/inverse_heat_source.gif +0 -0
  74. {fastlsq-0.1.4 → fastlsq-0.2.1}/pyproject.toml +9 -3
  75. {fastlsq-0.1.4 → fastlsq-0.2.1}/requirements.txt +1 -0
  76. fastlsq-0.2.1/tests/test_block.py +68 -0
  77. fastlsq-0.2.1/tests/test_derivatives.py +209 -0
  78. fastlsq-0.2.1/tests/test_device.py +43 -0
  79. fastlsq-0.2.1/tests/test_grad_shafranov.py +41 -0
  80. fastlsq-0.2.1/tests/test_grid_swing.py +40 -0
  81. fastlsq-0.2.1/tests/test_learnable.py +98 -0
  82. fastlsq-0.2.1/tests/test_orbit_hill.py +42 -0
  83. fastlsq-0.2.1/tests/test_vector_basis.py +220 -0
  84. fastlsq-0.1.4/FastLSQ.egg-info/SOURCES.txt +0 -65
  85. fastlsq-0.1.4/examples/inverse/aero_.py +0 -362
  86. fastlsq-0.1.4/examples/inverse/denoising_parameter_estimation.py +0 -297
  87. fastlsq-0.1.4/examples/inverse/elastic_wave_animation.py +0 -300
  88. fastlsq-0.1.4/examples/inverse/heat_from_video.py +0 -394
  89. fastlsq-0.1.4/examples/inverse/inverse_turbulence.py +0 -811
  90. fastlsq-0.1.4/examples/inverse/shape_ns.py +0 -1426
  91. fastlsq-0.1.4/examples/inverse/subsurface_imaging.py +0 -547
  92. fastlsq-0.1.4/examples/inverse/wing_optimize_simple.py +0 -283
  93. fastlsq-0.1.4/examples/sindy/compare_sindy_methods.py +0 -760
  94. fastlsq-0.1.4/examples/sindy/sindy_benchmarks.py +0 -351
  95. fastlsq-0.1.4/examples/sindy/sindy_differentiable.py +0 -418
  96. fastlsq-0.1.4/examples/sindy/sindy_minimal_diff.py +0 -716
  97. fastlsq-0.1.4/fastlsq/linalg.py +0 -34
  98. {fastlsq-0.1.4 → fastlsq-0.2.1}/FastLSQ.egg-info/dependency_links.txt +0 -0
  99. {fastlsq-0.1.4 → fastlsq-0.2.1}/FastLSQ.egg-info/top_level.txt +0 -0
  100. {fastlsq-0.1.4 → fastlsq-0.2.1}/LICENSE +0 -0
  101. {fastlsq-0.1.4 → fastlsq-0.2.1}/MANIFEST.in +0 -0
  102. {fastlsq-0.1.4 → fastlsq-0.2.1}/examples/add_your_own_pde.py +0 -0
  103. {fastlsq-0.1.4 → fastlsq-0.2.1}/examples/benchmark_comparison.py +0 -0
  104. {fastlsq-0.1.4 → fastlsq-0.2.1}/examples/custom_features.py +0 -0
  105. {fastlsq-0.1.4 → fastlsq-0.2.1}/examples/inverse_heat_source.py +0 -0
  106. {fastlsq-0.1.4 → fastlsq-0.2.1}/examples/inverse_magnetostatics.py +0 -0
  107. {fastlsq-0.1.4 → fastlsq-0.2.1}/examples/inverse_source_position.py +0 -0
  108. {fastlsq-0.1.4 → fastlsq-0.2.1}/examples/learnable_helmholtz.py +0 -0
  109. {fastlsq-0.1.4 → fastlsq-0.2.1}/examples/pde_discovery.py +0 -0
  110. {fastlsq-0.1.4 → fastlsq-0.2.1}/examples/run_all_extensions.py +0 -0
  111. {fastlsq-0.1.4 → fastlsq-0.2.1}/examples/run_linear.py +0 -0
  112. {fastlsq-0.1.4 → fastlsq-0.2.1}/examples/run_nonlinear.py +0 -0
  113. {fastlsq-0.1.4 → fastlsq-0.2.1}/examples/tutorial_basic.py +0 -0
  114. {fastlsq-0.1.4 → fastlsq-0.2.1}/examples/tutorial_nonlinear.py +0 -0
  115. {fastlsq-0.1.4 → fastlsq-0.2.1}/fastlsq/diagnostics.py +0 -0
  116. {fastlsq-0.1.4 → fastlsq-0.2.1}/fastlsq/export.py +0 -0
  117. {fastlsq-0.1.4 → fastlsq-0.2.1}/fastlsq/lightning.py +0 -0
  118. {fastlsq-0.1.4 → fastlsq-0.2.1}/fastlsq/plotting.py +0 -0
  119. {fastlsq-0.1.4 → fastlsq-0.2.1}/fastlsq/problems/linear.py +0 -0
  120. {fastlsq-0.1.4 → fastlsq-0.2.1}/fastlsq/problems/nonlinear.py +0 -0
  121. {fastlsq-0.1.4 → fastlsq-0.2.1}/fastlsq/problems/regression.py +0 -0
  122. {fastlsq-0.1.4 → fastlsq-0.2.1}/misc/fastlsq_teaser.png +0 -0
  123. {fastlsq-0.1.4 → fastlsq-0.2.1}/misc/ideal_quadrupole.png +0 -0
  124. {fastlsq-0.1.4 → fastlsq-0.2.1}/misc/inverse_heat_source.png +0 -0
  125. {fastlsq-0.1.4 → fastlsq-0.2.1}/misc/inverse_magnetostatics.png +0 -0
  126. {fastlsq-0.1.4 → fastlsq-0.2.1}/misc/inverse_magnetostatics_convergence.png +0 -0
  127. {fastlsq-0.1.4 → fastlsq-0.2.1}/misc/quadrupole_convergence.png +0 -0
  128. {fastlsq-0.1.4 → fastlsq-0.2.1}/misc/quadrupole_optimization.png +0 -0
  129. {fastlsq-0.1.4 → fastlsq-0.2.1}/misc/tutorial_nlpoisson_convergence.png +0 -0
  130. {fastlsq-0.1.4 → fastlsq-0.2.1}/misc/tutorial_nlpoisson_solution.png +0 -0
  131. {fastlsq-0.1.4 → fastlsq-0.2.1}/setup.cfg +0 -0
  132. {fastlsq-0.1.4 → fastlsq-0.2.1}/tests/test_basic.py +0 -0
@@ -2,11 +2,90 @@
2
2
 
3
3
  All notable changes to FastLSQ will be documented in this file.
4
4
 
5
- ## [Unreleased]
5
+ ## [0.2.1] - 2026-06-02
6
6
 
7
7
  ### Added
8
8
 
9
- - **Learnable operator coefficients**: `Op` now accepts `nn.Parameter` (and tensors) as coefficients in scalar multiplication. Use `k = nn.Parameter(...)` with `Op.laplacian(d=2) + k**2 * Op.identity(d=2)` and optimise via AdamW; gradients flow through the prebuilt linear solve. See `examples/learnable_helmholtz.py`.
9
+ - **Device abstraction** (`fastlsq/device.py`): `resolve_device`, `set_device`,
10
+ `get_device`, `device_info` for CPU / CUDA / Apple-MPS. dtype-aware -- MPS is
11
+ auto-selected only for float32 (it has no float64), so the default float64
12
+ high-accuracy regime stays on CPU/CUDA. Override with `set_device(...)` or the
13
+ `FASTLSQ_DEVICE` environment variable; internal tensor creation respects the
14
+ active device at call time.
15
+ - **Pluggable linear solver** `solve_lstsq(..., method=...)`:
16
+ - `"svd"` -- rank-revealing truncated SVD (LAPACK `gelsd` fast path on CPU);
17
+ - `"cholesky"` -- fast normal-equations solve for well-conditioned systems;
18
+ - `"rsvd"` -- torch-native randomized SVD (`O(MNk)`) for strongly low-rank `A`;
19
+ - `"auto"` (default) -- Cholesky with a cheap conditioning probe, falling back
20
+ to SVD when ill-conditioned (recovers the fast path without losing accuracy).
21
+ MPS factorizations run on CPU (no robust `svd`/`lstsq` there) and move back.
22
+ - **Working anisotropic Sigma = L Lᵀ learner**: `LearnableFastLSQ` (diagonal &
23
+ cholesky modes) now converges -- `solve_inner` uses a differentiable
24
+ *rank-revealing* solve, the Cholesky factor is log-parameterized (clamped,
25
+ positive-definite), and `train_bandwidth` is robust (gradient clipping,
26
+ best-iterate restore, graceful SVD/gradient-failure handling). Chainable
27
+ `LearnableFastLSQ.fit(problem, ...)` for one-line learn-then-predict.
28
+ - **Vector-valued solutions (`u: ℝᵈ → ℝᵏ`)**: first-class support for coupled and
29
+ decoupled multi-output PDEs via the new `fastlsq.block` module. Problems opt in
30
+ with `self.n_outputs = k`; `solver.beta` is `(N, k)` and `solver.predict(x)`
31
+ returns `(M, k)` (scalar `k=1` is bit-for-bit unchanged). `block_concat`
32
+ assembles a nested list of blocks (`None` = zero block); `pack_beta` /
33
+ `unpack_beta` convert between `(N, k)` and the block-stacked `(N*k, 1)` solve.
34
+ The block-stacked LSQ is solved by the rank-revealing solver, and the
35
+ Σ-learner computes its loss on the flat `_beta_flat` so it stays correct for
36
+ `k>1`.
37
+ - **Learnable operator coefficients**: `Op` accepts `nn.Parameter` (and tensors)
38
+ as coefficients, e.g. `Op.laplacian(d=2) + k**2 * Op.identity(d=2)` with
39
+ `k = nn.Parameter(...)`; gradients flow through the prebuilt linear solve.
40
+
41
+ ### Changed
42
+
43
+ - `solve_lstsq` defaults to the rank-revealing / `auto` solve instead of forming
44
+ the normal equations -- several orders of magnitude more accurate on the
45
+ rank-deficient random-feature systems (at a higher, still one-shot, cost).
46
+ - `solve_linear` is one-shot: the bandwidth-learning hyper-parameters live on
47
+ `LearnableFastLSQ.fit()` / `train_bandwidth`, not the solve signature.
48
+ - Packaging: `requires-python` lowered to `>=3.9` (+3.9 classifier); description
49
+ updated.
50
+
51
+ ### Fixed
52
+
53
+ - The previously dead `LearnableFastLSQ(mode="cholesky")` path, which diverged
54
+ from an unstable inner `torch.linalg.lstsq` and an unconstrained `L`.
55
+
56
+ ## [0.1.5] - 2026-05-25
57
+
58
+ ### Added
59
+
60
+ - **Vector-valued features**: new `VectorBasis` and `VectorFastLSQSolver`
61
+ (`fastlsq/vector.py`) for solving coupled systems where the unknown is a
62
+ vector field `u(x) = (u_1, ..., u_K)`. Use cases include
63
+ streamfunction-vorticity NS `(psi, omega)`, incompressible NS primitive
64
+ variables `(u, v, p)`, multi-species transport, MHD, etc.
65
+
66
+ - `VectorBasis.random(input_dim, n_features, sigma, n_components, sigmas=...)`
67
+ creates K independent random-Fourier `SinusoidalBasis`es; per-component
68
+ bandwidth can be tuned via `sigmas`.
69
+ - Stacked evaluators: `evaluate(x) -> (M, K, N)`, `gradient -> (M, K, d, N)`,
70
+ `laplacian`, `hessian_diag`, `derivative(alpha)`.
71
+ - Block-diagonal assembly helpers (`block_diag_evaluate`,
72
+ `block_diag_laplacian`, `block_diag_derivative`) for systems whose
73
+ rows are independent per component.
74
+ - Coefficient packing utilities (`stack_betas`, `unstack_beta`,
75
+ `predict`) accept per-component lists, stacked columns, or
76
+ `(N, K)` matrices interchangeably.
77
+ - `VectorFastLSQSolver(input_dim, n_components, normalize=True)` is the
78
+ multi-component counterpart of `FastLSQSolver`; `add_block(scale=...)`
79
+ accepts either a scalar or a list of K scalars for per-component
80
+ bandwidth.
81
+ - `component(k)` / `component_solver(k)` give direct access to the k-th
82
+ scalar basis / solver for ad-hoc per-component work.
83
+
84
+ ### Other
85
+
86
+ - Synchronised `pyproject.toml`, `fastlsq.__version__`, and CHANGELOG (the
87
+ package source had drifted to `__version__ = "0.1.0"` against
88
+ `pyproject.toml = "0.1.4"`; both now read `"0.1.5"`).
10
89
 
11
90
  ## [0.2.0] - 2026-03-01
12
91
 
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: FastLSQ
3
- Version: 0.1.4
4
- Summary: Solving PDEs in one shot via Fourier features with exact analytical derivatives
3
+ Version: 0.2.1
4
+ Summary: One-shot PDE solving via Fourier features with exact analytical derivatives; rank-revealing solvers, learnable anisotropic bandwidth, and CPU/CUDA/MPS support
5
5
  Author: Antonin Sulc
6
6
  License-Expression: MIT
7
7
  Project-URL: Homepage, https://github.com/asulc/FastLSQ
@@ -14,17 +14,22 @@ Classifier: Development Status :: 4 - Beta
14
14
  Classifier: Intended Audience :: Science/Research
15
15
  Classifier: Operating System :: OS Independent
16
16
  Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
17
18
  Classifier: Programming Language :: Python :: 3.10
18
19
  Classifier: Programming Language :: Python :: 3.11
19
20
  Classifier: Programming Language :: Python :: 3.12
20
21
  Classifier: Topic :: Scientific/Engineering :: Mathematics
21
22
  Classifier: Topic :: Scientific/Engineering :: Physics
22
- Requires-Python: >=3.10
23
+ Requires-Python: >=3.9
23
24
  Description-Content-Type: text/markdown
24
25
  License-File: LICENSE
25
26
  Requires-Dist: torch>=2.0
26
27
  Requires-Dist: numpy>=1.24
27
28
  Requires-Dist: matplotlib>=3.7
29
+ Provides-Extra: battery
30
+ Requires-Dist: progpy>=1.3; extra == "battery"
31
+ Requires-Dist: pandas>=2.0; extra == "battery"
32
+ Requires-Dist: scipy>=1.10; extra == "battery"
28
33
  Provides-Extra: dev
29
34
  Requires-Dist: pytest>=7.0; extra == "dev"
30
35
  Requires-Dist: pandas>=2.0; extra == "dev"
@@ -36,8 +41,11 @@ Dynamic: license-file
36
41
 
37
42
  # FastLSQ
38
43
 
44
+ [BerkeleyLab ATAP Talk](https://github.com/sulcantonin/FastLSQ/raw/main/presentations/ATAP_Sulc_20260324.pptx)
45
+
46
+
39
47
  <p align="center">
40
- <img src="misc/fastlsq_teaser.png" alt="FastLSQ method overview" width="800"/>
48
+ <img src="misc/fastlsq_teaser.png" alt="FastLSQ method overview" width="400"/>
41
49
  </p>
42
50
 
43
51
  **Solving PDEs in one shot via Fourier features with exact analytical derivatives.**
@@ -128,6 +136,78 @@ A_pde = helmholtz.apply(basis, x) # (5000, 1500)
128
136
  wave = Op.partial(dim=2, order=2, d=3) - c**2 * Op.laplacian(d=3, dims=[0, 1])
129
137
  ```
130
138
 
139
+ ### Vector-valued solutions
140
+
141
+ `solve_linear` / `solve_nonlinear` support vector-valued **u**: ℝᵈ → ℝᵏ for
142
+ coupled systems (elasticity, Stokes, Maxwell vector potential, …) and for
143
+ decoupled multi-output problems sharing one basis. The math is unchanged; the
144
+ solver just allocates `beta` with shape `(N, k)` so that `solver.predict(x)`
145
+ returns shape `(M, k)` directly.
146
+
147
+ A problem opts in by setting `self.n_outputs = k` and assembling its operator
148
+ in block-stacked form `A ∈ ℝ^{Mk × Nk}`, `b ∈ ℝ^{Mk × 1}`. The helper
149
+ `block_concat` removes the manual `torch.cat` bookkeeping:
150
+
151
+ ```python
152
+ import torch
153
+ from fastlsq import solve_linear, block_concat
154
+
155
+ class Stokes2D:
156
+ n_outputs = 3 # (u, v, p)
157
+ dim = 2
158
+ name = "Stokes 2D"
159
+ # ... exact, exact_grad, get_train_data, get_test_points ...
160
+
161
+ def build(self, slv, x, bcs, f):
162
+ basis = slv.basis
163
+ cache = basis.cache(x)
164
+ dx = basis.derivative(x, (1, 0), cache=cache)
165
+ dy = basis.derivative(x, (0, 1), cache=cache)
166
+ lap = basis.laplacian(x, cache=cache)
167
+
168
+ # Rows = equations (mom_x, mom_y, continuity);
169
+ # columns = coefficient blocks (u, v, p)
170
+ A = block_concat([
171
+ [-lap, None, dx ], # -Δu + ∂p/∂x = f_x
172
+ [ None, -lap, dy ], # -Δv + ∂p/∂y = f_y
173
+ [ dx, dy, None], # ∂u/∂x + ∂v/∂y = 0
174
+ ])
175
+ b = block_concat([[f[:, 0:1]], [f[:, 1:2]], [torch.zeros_like(f[:, 0:1])]])
176
+ # ... add BC blocks the same way ...
177
+ return A, b
178
+
179
+ result = solve_linear(Stokes2D(), scale=5.0)
180
+ u = result["u_fn"](x_test) # shape (M, 3): columns are (u, v, p)
181
+ ```
182
+
183
+ #### Partial derivatives for a vector u
184
+
185
+ The basis-level operators (`basis.derivative`, `basis.gradient`,
186
+ `basis.laplacian`, `DiffOperator.apply`) all return shape `(M, N)` regardless
187
+ of how many components `u` has — vector-ness only enters when you contract
188
+ with `beta`:
189
+
190
+ ```python
191
+ # Full Jacobian, then slice (M, d, k) -> per (component, dim)
192
+ u, J = solver.predict_with_grad(x) # J shape (M, d, k); J[:, j, c] = ∂u_c/∂x_j
193
+
194
+ # Single operator on a single component
195
+ D_y = solver.basis.derivative(x, alpha=(0, 1)) # (M, N): ∂φ/∂y
196
+ du0_dy = D_y @ solver.beta[:, 0:1] # ∂u_0/∂y
197
+
198
+ # Symbolic operator, all components at once
199
+ from fastlsq import Op
200
+ yy = Op.partial(dim=1, order=2, d=2)
201
+ A = yy.apply(solver.basis, x) # (M, N)
202
+ u_yy = A @ solver.beta # (M, k): ∂²u/∂y² per component
203
+ ```
204
+
205
+ Scalar problems are untouched: `n_outputs` defaults to `1`, `solver.beta` keeps
206
+ shape `(N, 1)`, and `predict_with_grad` returns gradient shape `(M, d)` for
207
+ backward compatibility (the trailing component axis is squeezed when k=1).
208
+ `ElasticWave2D` in [fastlsq/problems/linear.py](fastlsq/problems/linear.py) is
209
+ the canonical coupled vector example.
210
+
131
211
  ### Plot solutions
132
212
 
133
213
  ```python
@@ -177,6 +257,7 @@ derivative engine:
177
257
  | `FeatureBasis` | Adapter for non-sinusoidal solvers (e.g. PIELM with tanh) |
178
258
  | `FastLSQSolver` | Manages feature blocks; exposes `.basis` for all derivative computations |
179
259
  | `LearnableFastLSQ` | Differentiable solver with learnable bandwidth via reparameterisation trick |
260
+ | `block_concat`, `pack_beta`, `unpack_beta` | Block-structured assembly helpers for vector-valued **u** (coupled systems). `solver.beta` has shape `(N, k)`; scalar problems are the k=1 case |
180
261
 
181
262
  ### How it works
182
263
 
@@ -253,6 +334,7 @@ See `examples/add_your_own_pde.py` for the complete tutorial.
253
334
 
254
335
  - **Analytical derivative engine**: `SinusoidalBasis` computes arbitrary-order derivatives exactly in O(1) -- the foundation of the entire framework
255
336
  - **Symbolic PDE operators**: Compose differential operators with `Op` (Laplacian, wave, Helmholtz, biharmonic, custom) via intuitive arithmetic; coefficients can be `nn.Parameter` for AdamW optimisation
337
+ - **Vector-valued solutions**: First-class support for **u**: ℝᵈ → ℝᵏ (elasticity, Stokes, Maxwell). Problems declare `n_outputs = k`; `block_concat` assembles coupled block systems; `solver.predict(x)` returns shape `(M, k)`. Scalar problems are the `k=1` case
256
338
  - **High-level API**: Solve PDEs in one line with `solve_linear()` and `solve_nonlinear()`
257
339
  - **Learnable bandwidth**: `LearnableFastLSQ` optimises the bandwidth (scalar or anisotropic) via reparameterisation
258
340
  - **Learnable PDE coefficients**: Plug `nn.Parameter` into `Op` (e.g. Helmholtz wavenumber `k`) and optimise via AdamW; gradients flow through the prebuilt linear solve
@@ -0,0 +1,116 @@
1
+ CHANGELOG.md
2
+ LICENSE
3
+ MANIFEST.in
4
+ README.md
5
+ pyproject.toml
6
+ requirements.txt
7
+ FastLSQ.egg-info/PKG-INFO
8
+ FastLSQ.egg-info/SOURCES.txt
9
+ FastLSQ.egg-info/dependency_links.txt
10
+ FastLSQ.egg-info/requires.txt
11
+ FastLSQ.egg-info/top_level.txt
12
+ examples/add_your_own_pde.py
13
+ examples/benchmark_comparison.py
14
+ examples/custom_features.py
15
+ examples/grad_shafranov.py
16
+ examples/grid_inverse.py
17
+ examples/grid_rl_control.py
18
+ examples/grid_swing.py
19
+ examples/gs_inverse.py
20
+ examples/gs_rl_control.py
21
+ examples/inverse_heat_source.py
22
+ examples/inverse_magnetostatics.py
23
+ examples/inverse_source_position.py
24
+ examples/learnable_helmholtz.py
25
+ examples/orbit_hill.py
26
+ examples/orbit_inverse.py
27
+ examples/orbit_rl.py
28
+ examples/pde_discovery.py
29
+ examples/run_all_extensions.py
30
+ examples/run_linear.py
31
+ examples/run_nonlinear.py
32
+ examples/tutorial_basic.py
33
+ examples/tutorial_nonlinear.py
34
+ examples/vector_basis_stream_vorticity.py
35
+ examples/extras/fred_sde.py
36
+ examples/extras/fred_sde_fastlsq.py
37
+ examples/extras/gaia_potential.py
38
+ examples/extras/gaia_potential_fastlsq.py
39
+ examples/extras/horizons_ephemeris.py
40
+ examples/extras/numerai_alpha.py
41
+ examples/extras/numerai_alpha_fastlsq.py
42
+ examples/extras/run_all_fastlsq.py
43
+ examples/extras/spectral_expansion.py
44
+ examples/extras/scenarios/__init__.py
45
+ examples/extras/scenarios/_alsu_lattice.py
46
+ examples/extras/scenarios/_common.py
47
+ examples/extras/scenarios/run_all.py
48
+ examples/extras/scenarios/s01_beamloss_ode.py
49
+ examples/extras/scenarios/s01_betatron_tune.py
50
+ examples/extras/scenarios/s01_green_fff.py
51
+ examples/extras/scenarios/s01_hill_ivp.py
52
+ examples/extras/scenarios/s01_observe_fit_act_simulator.py
53
+ examples/extras/scenarios/s01_orbit_inverse.py
54
+ examples/extras/scenarios/s01_passive_loco.py
55
+ examples/extras/scenarios/s01_perturbed_hill.py
56
+ examples/extras/scenarios/s01_sofb_observe_fit_act.py
57
+ examples/extras/scenarios/s01_streaming_archive_growth.py
58
+ examples/extras/scenarios/s01_synchrotron_ode.py
59
+ examples/extras/scenarios/s01_tides_3months.py
60
+ examples/extras/scenarios/s01_topoff_impulse.py
61
+ examples/extras/scenarios/s01_visualize.py
62
+ examples/extras/scenarios/s02_plasma_wakefield.py
63
+ examples/extras/scenarios/s03_synchrobetatron.py
64
+ examples/extras/scenarios/s04_sunspots.py
65
+ examples/extras/scenarios/s05_helioseismology.py
66
+ examples/extras/scenarios/s06_tides.py
67
+ examples/extras/scenarios/s07_iers_earth_rotation.py
68
+ examples/extras/scenarios/s08_mauna_loa_co2.py
69
+ examples/extras/scenarios/s09_enso_qbo.py
70
+ examples/extras/scenarios/s10_pulsar_timing.py
71
+ examples/extras/scenarios/s11_modal_analysis.py
72
+ examples/extras/scenarios/s12_mems_resonator.py
73
+ examples/extras/scenarios/s13_variable_stars_kepler.py
74
+ examples/extras/scenarios/s14_eeg.py
75
+ examples/extras/scenarios/s15_circadian.py
76
+ fastlsq/__init__.py
77
+ fastlsq/api.py
78
+ fastlsq/basis.py
79
+ fastlsq/block.py
80
+ fastlsq/device.py
81
+ fastlsq/diagnostics.py
82
+ fastlsq/export.py
83
+ fastlsq/geometry.py
84
+ fastlsq/learnable.py
85
+ fastlsq/lightning.py
86
+ fastlsq/linalg.py
87
+ fastlsq/newton.py
88
+ fastlsq/plotting.py
89
+ fastlsq/solvers.py
90
+ fastlsq/tuning.py
91
+ fastlsq/utils.py
92
+ fastlsq/vector.py
93
+ fastlsq/viz.py
94
+ fastlsq/problems/__init__.py
95
+ fastlsq/problems/linear.py
96
+ fastlsq/problems/nonlinear.py
97
+ fastlsq/problems/regression.py
98
+ misc/fastlsq_teaser.png
99
+ misc/ideal_quadrupole.png
100
+ misc/inverse_heat_source.gif
101
+ misc/inverse_heat_source.png
102
+ misc/inverse_magnetostatics.png
103
+ misc/inverse_magnetostatics_convergence.png
104
+ misc/quadrupole_convergence.png
105
+ misc/quadrupole_optimization.png
106
+ misc/tutorial_nlpoisson_convergence.png
107
+ misc/tutorial_nlpoisson_solution.png
108
+ tests/test_basic.py
109
+ tests/test_block.py
110
+ tests/test_derivatives.py
111
+ tests/test_device.py
112
+ tests/test_grad_shafranov.py
113
+ tests/test_grid_swing.py
114
+ tests/test_learnable.py
115
+ tests/test_orbit_hill.py
116
+ tests/test_vector_basis.py
@@ -2,6 +2,11 @@ torch>=2.0
2
2
  numpy>=1.24
3
3
  matplotlib>=3.7
4
4
 
5
+ [battery]
6
+ progpy>=1.3
7
+ pandas>=2.0
8
+ scipy>=1.10
9
+
5
10
  [dev]
6
11
  pytest>=7.0
7
12
  pandas>=2.0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: FastLSQ
3
- Version: 0.1.4
4
- Summary: Solving PDEs in one shot via Fourier features with exact analytical derivatives
3
+ Version: 0.2.1
4
+ Summary: One-shot PDE solving via Fourier features with exact analytical derivatives; rank-revealing solvers, learnable anisotropic bandwidth, and CPU/CUDA/MPS support
5
5
  Author: Antonin Sulc
6
6
  License-Expression: MIT
7
7
  Project-URL: Homepage, https://github.com/asulc/FastLSQ
@@ -14,17 +14,22 @@ Classifier: Development Status :: 4 - Beta
14
14
  Classifier: Intended Audience :: Science/Research
15
15
  Classifier: Operating System :: OS Independent
16
16
  Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
17
18
  Classifier: Programming Language :: Python :: 3.10
18
19
  Classifier: Programming Language :: Python :: 3.11
19
20
  Classifier: Programming Language :: Python :: 3.12
20
21
  Classifier: Topic :: Scientific/Engineering :: Mathematics
21
22
  Classifier: Topic :: Scientific/Engineering :: Physics
22
- Requires-Python: >=3.10
23
+ Requires-Python: >=3.9
23
24
  Description-Content-Type: text/markdown
24
25
  License-File: LICENSE
25
26
  Requires-Dist: torch>=2.0
26
27
  Requires-Dist: numpy>=1.24
27
28
  Requires-Dist: matplotlib>=3.7
29
+ Provides-Extra: battery
30
+ Requires-Dist: progpy>=1.3; extra == "battery"
31
+ Requires-Dist: pandas>=2.0; extra == "battery"
32
+ Requires-Dist: scipy>=1.10; extra == "battery"
28
33
  Provides-Extra: dev
29
34
  Requires-Dist: pytest>=7.0; extra == "dev"
30
35
  Requires-Dist: pandas>=2.0; extra == "dev"
@@ -36,8 +41,11 @@ Dynamic: license-file
36
41
 
37
42
  # FastLSQ
38
43
 
44
+ [BerkeleyLab ATAP Talk](https://github.com/sulcantonin/FastLSQ/raw/main/presentations/ATAP_Sulc_20260324.pptx)
45
+
46
+
39
47
  <p align="center">
40
- <img src="misc/fastlsq_teaser.png" alt="FastLSQ method overview" width="800"/>
48
+ <img src="misc/fastlsq_teaser.png" alt="FastLSQ method overview" width="400"/>
41
49
  </p>
42
50
 
43
51
  **Solving PDEs in one shot via Fourier features with exact analytical derivatives.**
@@ -128,6 +136,78 @@ A_pde = helmholtz.apply(basis, x) # (5000, 1500)
128
136
  wave = Op.partial(dim=2, order=2, d=3) - c**2 * Op.laplacian(d=3, dims=[0, 1])
129
137
  ```
130
138
 
139
+ ### Vector-valued solutions
140
+
141
+ `solve_linear` / `solve_nonlinear` support vector-valued **u**: ℝᵈ → ℝᵏ for
142
+ coupled systems (elasticity, Stokes, Maxwell vector potential, …) and for
143
+ decoupled multi-output problems sharing one basis. The math is unchanged; the
144
+ solver just allocates `beta` with shape `(N, k)` so that `solver.predict(x)`
145
+ returns shape `(M, k)` directly.
146
+
147
+ A problem opts in by setting `self.n_outputs = k` and assembling its operator
148
+ in block-stacked form `A ∈ ℝ^{Mk × Nk}`, `b ∈ ℝ^{Mk × 1}`. The helper
149
+ `block_concat` removes the manual `torch.cat` bookkeeping:
150
+
151
+ ```python
152
+ import torch
153
+ from fastlsq import solve_linear, block_concat
154
+
155
+ class Stokes2D:
156
+ n_outputs = 3 # (u, v, p)
157
+ dim = 2
158
+ name = "Stokes 2D"
159
+ # ... exact, exact_grad, get_train_data, get_test_points ...
160
+
161
+ def build(self, slv, x, bcs, f):
162
+ basis = slv.basis
163
+ cache = basis.cache(x)
164
+ dx = basis.derivative(x, (1, 0), cache=cache)
165
+ dy = basis.derivative(x, (0, 1), cache=cache)
166
+ lap = basis.laplacian(x, cache=cache)
167
+
168
+ # Rows = equations (mom_x, mom_y, continuity);
169
+ # columns = coefficient blocks (u, v, p)
170
+ A = block_concat([
171
+ [-lap, None, dx ], # -Δu + ∂p/∂x = f_x
172
+ [ None, -lap, dy ], # -Δv + ∂p/∂y = f_y
173
+ [ dx, dy, None], # ∂u/∂x + ∂v/∂y = 0
174
+ ])
175
+ b = block_concat([[f[:, 0:1]], [f[:, 1:2]], [torch.zeros_like(f[:, 0:1])]])
176
+ # ... add BC blocks the same way ...
177
+ return A, b
178
+
179
+ result = solve_linear(Stokes2D(), scale=5.0)
180
+ u = result["u_fn"](x_test) # shape (M, 3): columns are (u, v, p)
181
+ ```
182
+
183
+ #### Partial derivatives for a vector u
184
+
185
+ The basis-level operators (`basis.derivative`, `basis.gradient`,
186
+ `basis.laplacian`, `DiffOperator.apply`) all return shape `(M, N)` regardless
187
+ of how many components `u` has — vector-ness only enters when you contract
188
+ with `beta`:
189
+
190
+ ```python
191
+ # Full Jacobian, then slice (M, d, k) -> per (component, dim)
192
+ u, J = solver.predict_with_grad(x) # J shape (M, d, k); J[:, j, c] = ∂u_c/∂x_j
193
+
194
+ # Single operator on a single component
195
+ D_y = solver.basis.derivative(x, alpha=(0, 1)) # (M, N): ∂φ/∂y
196
+ du0_dy = D_y @ solver.beta[:, 0:1] # ∂u_0/∂y
197
+
198
+ # Symbolic operator, all components at once
199
+ from fastlsq import Op
200
+ yy = Op.partial(dim=1, order=2, d=2)
201
+ A = yy.apply(solver.basis, x) # (M, N)
202
+ u_yy = A @ solver.beta # (M, k): ∂²u/∂y² per component
203
+ ```
204
+
205
+ Scalar problems are untouched: `n_outputs` defaults to `1`, `solver.beta` keeps
206
+ shape `(N, 1)`, and `predict_with_grad` returns gradient shape `(M, d)` for
207
+ backward compatibility (the trailing component axis is squeezed when k=1).
208
+ `ElasticWave2D` in [fastlsq/problems/linear.py](fastlsq/problems/linear.py) is
209
+ the canonical coupled vector example.
210
+
131
211
  ### Plot solutions
132
212
 
133
213
  ```python
@@ -177,6 +257,7 @@ derivative engine:
177
257
  | `FeatureBasis` | Adapter for non-sinusoidal solvers (e.g. PIELM with tanh) |
178
258
  | `FastLSQSolver` | Manages feature blocks; exposes `.basis` for all derivative computations |
179
259
  | `LearnableFastLSQ` | Differentiable solver with learnable bandwidth via reparameterisation trick |
260
+ | `block_concat`, `pack_beta`, `unpack_beta` | Block-structured assembly helpers for vector-valued **u** (coupled systems). `solver.beta` has shape `(N, k)`; scalar problems are the k=1 case |
180
261
 
181
262
  ### How it works
182
263
 
@@ -253,6 +334,7 @@ See `examples/add_your_own_pde.py` for the complete tutorial.
253
334
 
254
335
  - **Analytical derivative engine**: `SinusoidalBasis` computes arbitrary-order derivatives exactly in O(1) -- the foundation of the entire framework
255
336
  - **Symbolic PDE operators**: Compose differential operators with `Op` (Laplacian, wave, Helmholtz, biharmonic, custom) via intuitive arithmetic; coefficients can be `nn.Parameter` for AdamW optimisation
337
+ - **Vector-valued solutions**: First-class support for **u**: ℝᵈ → ℝᵏ (elasticity, Stokes, Maxwell). Problems declare `n_outputs = k`; `block_concat` assembles coupled block systems; `solver.predict(x)` returns shape `(M, k)`. Scalar problems are the `k=1` case
256
338
  - **High-level API**: Solve PDEs in one line with `solve_linear()` and `solve_nonlinear()`
257
339
  - **Learnable bandwidth**: `LearnableFastLSQ` optimises the bandwidth (scalar or anisotropic) via reparameterisation
258
340
  - **Learnable PDE coefficients**: Plug `nn.Parameter` into `Op` (e.g. Helmholtz wavenumber `k`) and optimise via AdamW; gradients flow through the prebuilt linear solve
@@ -1,7 +1,10 @@
1
1
  # FastLSQ
2
2
 
3
+ [BerkeleyLab ATAP Talk](https://github.com/sulcantonin/FastLSQ/raw/main/presentations/ATAP_Sulc_20260324.pptx)
4
+
5
+
3
6
  <p align="center">
4
- <img src="misc/fastlsq_teaser.png" alt="FastLSQ method overview" width="800"/>
7
+ <img src="misc/fastlsq_teaser.png" alt="FastLSQ method overview" width="400"/>
5
8
  </p>
6
9
 
7
10
  **Solving PDEs in one shot via Fourier features with exact analytical derivatives.**
@@ -92,6 +95,78 @@ A_pde = helmholtz.apply(basis, x) # (5000, 1500)
92
95
  wave = Op.partial(dim=2, order=2, d=3) - c**2 * Op.laplacian(d=3, dims=[0, 1])
93
96
  ```
94
97
 
98
+ ### Vector-valued solutions
99
+
100
+ `solve_linear` / `solve_nonlinear` support vector-valued **u**: ℝᵈ → ℝᵏ for
101
+ coupled systems (elasticity, Stokes, Maxwell vector potential, …) and for
102
+ decoupled multi-output problems sharing one basis. The math is unchanged; the
103
+ solver just allocates `beta` with shape `(N, k)` so that `solver.predict(x)`
104
+ returns shape `(M, k)` directly.
105
+
106
+ A problem opts in by setting `self.n_outputs = k` and assembling its operator
107
+ in block-stacked form `A ∈ ℝ^{Mk × Nk}`, `b ∈ ℝ^{Mk × 1}`. The helper
108
+ `block_concat` removes the manual `torch.cat` bookkeeping:
109
+
110
+ ```python
111
+ import torch
112
+ from fastlsq import solve_linear, block_concat
113
+
114
+ class Stokes2D:
115
+ n_outputs = 3 # (u, v, p)
116
+ dim = 2
117
+ name = "Stokes 2D"
118
+ # ... exact, exact_grad, get_train_data, get_test_points ...
119
+
120
+ def build(self, slv, x, bcs, f):
121
+ basis = slv.basis
122
+ cache = basis.cache(x)
123
+ dx = basis.derivative(x, (1, 0), cache=cache)
124
+ dy = basis.derivative(x, (0, 1), cache=cache)
125
+ lap = basis.laplacian(x, cache=cache)
126
+
127
+ # Rows = equations (mom_x, mom_y, continuity);
128
+ # columns = coefficient blocks (u, v, p)
129
+ A = block_concat([
130
+ [-lap, None, dx ], # -Δu + ∂p/∂x = f_x
131
+ [ None, -lap, dy ], # -Δv + ∂p/∂y = f_y
132
+ [ dx, dy, None], # ∂u/∂x + ∂v/∂y = 0
133
+ ])
134
+ b = block_concat([[f[:, 0:1]], [f[:, 1:2]], [torch.zeros_like(f[:, 0:1])]])
135
+ # ... add BC blocks the same way ...
136
+ return A, b
137
+
138
+ result = solve_linear(Stokes2D(), scale=5.0)
139
+ u = result["u_fn"](x_test) # shape (M, 3): columns are (u, v, p)
140
+ ```
141
+
142
+ #### Partial derivatives for a vector u
143
+
144
+ The basis-level operators (`basis.derivative`, `basis.gradient`,
145
+ `basis.laplacian`, `DiffOperator.apply`) all return shape `(M, N)` regardless
146
+ of how many components `u` has — vector-ness only enters when you contract
147
+ with `beta`:
148
+
149
+ ```python
150
+ # Full Jacobian, then slice (M, d, k) -> per (component, dim)
151
+ u, J = solver.predict_with_grad(x) # J shape (M, d, k); J[:, j, c] = ∂u_c/∂x_j
152
+
153
+ # Single operator on a single component
154
+ D_y = solver.basis.derivative(x, alpha=(0, 1)) # (M, N): ∂φ/∂y
155
+ du0_dy = D_y @ solver.beta[:, 0:1] # ∂u_0/∂y
156
+
157
+ # Symbolic operator, all components at once
158
+ from fastlsq import Op
159
+ yy = Op.partial(dim=1, order=2, d=2)
160
+ A = yy.apply(solver.basis, x) # (M, N)
161
+ u_yy = A @ solver.beta # (M, k): ∂²u/∂y² per component
162
+ ```
163
+
164
+ Scalar problems are untouched: `n_outputs` defaults to `1`, `solver.beta` keeps
165
+ shape `(N, 1)`, and `predict_with_grad` returns gradient shape `(M, d)` for
166
+ backward compatibility (the trailing component axis is squeezed when k=1).
167
+ `ElasticWave2D` in [fastlsq/problems/linear.py](fastlsq/problems/linear.py) is
168
+ the canonical coupled vector example.
169
+
95
170
  ### Plot solutions
96
171
 
97
172
  ```python
@@ -141,6 +216,7 @@ derivative engine:
141
216
  | `FeatureBasis` | Adapter for non-sinusoidal solvers (e.g. PIELM with tanh) |
142
217
  | `FastLSQSolver` | Manages feature blocks; exposes `.basis` for all derivative computations |
143
218
  | `LearnableFastLSQ` | Differentiable solver with learnable bandwidth via reparameterisation trick |
219
+ | `block_concat`, `pack_beta`, `unpack_beta` | Block-structured assembly helpers for vector-valued **u** (coupled systems). `solver.beta` has shape `(N, k)`; scalar problems are the k=1 case |
144
220
 
145
221
  ### How it works
146
222
 
@@ -217,6 +293,7 @@ See `examples/add_your_own_pde.py` for the complete tutorial.
217
293
 
218
294
  - **Analytical derivative engine**: `SinusoidalBasis` computes arbitrary-order derivatives exactly in O(1) -- the foundation of the entire framework
219
295
  - **Symbolic PDE operators**: Compose differential operators with `Op` (Laplacian, wave, Helmholtz, biharmonic, custom) via intuitive arithmetic; coefficients can be `nn.Parameter` for AdamW optimisation
296
+ - **Vector-valued solutions**: First-class support for **u**: ℝᵈ → ℝᵏ (elasticity, Stokes, Maxwell). Problems declare `n_outputs = k`; `block_concat` assembles coupled block systems; `solver.predict(x)` returns shape `(M, k)`. Scalar problems are the `k=1` case
220
297
  - **High-level API**: Solve PDEs in one line with `solve_linear()` and `solve_nonlinear()`
221
298
  - **Learnable bandwidth**: `LearnableFastLSQ` optimises the bandwidth (scalar or anisotropic) via reparameterisation
222
299
  - **Learnable PDE coefficients**: Plug `nn.Parameter` into `Op` (e.g. Helmholtz wavenumber `k`) and optimise via AdamW; gradients flow through the prebuilt linear solve