FastLSQ 0.2.4__tar.gz → 0.2.6__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 (142) hide show
  1. {fastlsq-0.2.4 → fastlsq-0.2.6}/CHANGELOG.md +37 -4
  2. {fastlsq-0.2.4 → fastlsq-0.2.6}/FastLSQ.egg-info/PKG-INFO +47 -11
  3. {fastlsq-0.2.4 → fastlsq-0.2.6}/FastLSQ.egg-info/SOURCES.txt +33 -0
  4. {fastlsq-0.2.4 → fastlsq-0.2.6}/PKG-INFO +47 -11
  5. {fastlsq-0.2.4 → fastlsq-0.2.6}/README.md +46 -10
  6. fastlsq-0.2.6/examples/digital_twins/darcy_heat.py +583 -0
  7. fastlsq-0.2.6/examples/digital_twins/pendulum.py +242 -0
  8. fastlsq-0.2.6/examples/digital_twins/pendulum_benchmark.py +281 -0
  9. fastlsq-0.2.6/examples/digital_twins/plasma_wakefield.py +339 -0
  10. fastlsq-0.2.6/examples/digital_twins/plasma_wakefield_2D_1.py +863 -0
  11. fastlsq-0.2.6/examples/digital_twins/plasma_wakefield_2D_2.py +769 -0
  12. fastlsq-0.2.6/examples/digital_twins/plasma_wakefield_2d_3.py +818 -0
  13. fastlsq-0.2.6/examples/digital_twins/plasma_wakefield_parameteric.py +475 -0
  14. fastlsq-0.2.6/examples/digital_twins/plot_utils.py +42 -0
  15. fastlsq-0.2.6/examples/digital_twins/structural_health_simple.py +343 -0
  16. fastlsq-0.2.6/examples/digital_twins/turbulence_gravity_cooling.py +387 -0
  17. fastlsq-0.2.6/examples/inverse/aero_.py +362 -0
  18. fastlsq-0.2.6/examples/inverse/denoising_parameter_estimation.py +297 -0
  19. fastlsq-0.2.6/examples/inverse/elastic_wave_animation.py +300 -0
  20. fastlsq-0.2.6/examples/inverse/heat_from_video.py +394 -0
  21. fastlsq-0.2.6/examples/inverse/inverse_turbulence.py +811 -0
  22. fastlsq-0.2.6/examples/inverse/shape_ns.py +1426 -0
  23. fastlsq-0.2.6/examples/inverse/subsurface_imaging.py +547 -0
  24. fastlsq-0.2.6/examples/inverse/wing_optimize_simple.py +283 -0
  25. fastlsq-0.2.6/examples/sindy/compare_sindy_methods.py +760 -0
  26. fastlsq-0.2.6/examples/sindy/sindy_benchmarks.py +351 -0
  27. fastlsq-0.2.6/examples/sindy/sindy_differentiable.py +418 -0
  28. fastlsq-0.2.6/examples/sindy/sindy_minimal_diff.py +716 -0
  29. {fastlsq-0.2.4 → fastlsq-0.2.6}/fastlsq/__init__.py +1 -1
  30. {fastlsq-0.2.4 → fastlsq-0.2.6}/fastlsq/linalg.py +7 -2
  31. {fastlsq-0.2.4 → fastlsq-0.2.6}/fastlsq/problems/linear.py +74 -34
  32. fastlsq-0.2.6/misc/fastlsq_teaser.png +0 -0
  33. fastlsq-0.2.6/misc/ideal_quadrupole.png +0 -0
  34. fastlsq-0.2.6/misc/inverse_heat_source.gif +0 -0
  35. fastlsq-0.2.6/misc/inverse_heat_source.png +0 -0
  36. fastlsq-0.2.6/misc/inverse_magnetostatics.png +0 -0
  37. fastlsq-0.2.6/misc/inverse_magnetostatics_convergence.png +0 -0
  38. fastlsq-0.2.6/misc/quadrupole_convergence.png +0 -0
  39. fastlsq-0.2.6/misc/quadrupole_optimization.png +0 -0
  40. fastlsq-0.2.6/misc/tutorial_nlpoisson_convergence.png +0 -0
  41. fastlsq-0.2.6/misc/tutorial_nlpoisson_solution.png +0 -0
  42. {fastlsq-0.2.4 → fastlsq-0.2.6}/pyproject.toml +1 -1
  43. {fastlsq-0.2.4 → fastlsq-0.2.6}/tests/test_benchmarks_inverse.py +28 -16
  44. {fastlsq-0.2.4 → fastlsq-0.2.6}/tests/test_vector_basis.py +1 -1
  45. {fastlsq-0.2.4 → fastlsq-0.2.6}/FastLSQ.egg-info/dependency_links.txt +0 -0
  46. {fastlsq-0.2.4 → fastlsq-0.2.6}/FastLSQ.egg-info/requires.txt +0 -0
  47. {fastlsq-0.2.4 → fastlsq-0.2.6}/FastLSQ.egg-info/top_level.txt +0 -0
  48. {fastlsq-0.2.4 → fastlsq-0.2.6}/LICENSE +0 -0
  49. {fastlsq-0.2.4 → fastlsq-0.2.6}/MANIFEST.in +0 -0
  50. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/add_your_own_pde.py +0 -0
  51. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/benchmark_comparison.py +0 -0
  52. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/custom_features.py +0 -0
  53. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/fred_sde.py +0 -0
  54. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/fred_sde_fastlsq.py +0 -0
  55. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/gaia_potential.py +0 -0
  56. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/gaia_potential_fastlsq.py +0 -0
  57. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/horizons_ephemeris.py +0 -0
  58. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/numerai_alpha.py +0 -0
  59. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/numerai_alpha_fastlsq.py +0 -0
  60. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/run_all_fastlsq.py +0 -0
  61. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/scenarios/__init__.py +0 -0
  62. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/scenarios/_alsu_lattice.py +0 -0
  63. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/scenarios/_common.py +0 -0
  64. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/scenarios/run_all.py +0 -0
  65. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/scenarios/s01_beamloss_ode.py +0 -0
  66. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/scenarios/s01_betatron_tune.py +0 -0
  67. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/scenarios/s01_green_fff.py +0 -0
  68. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/scenarios/s01_hill_ivp.py +0 -0
  69. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/scenarios/s01_observe_fit_act_simulator.py +0 -0
  70. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/scenarios/s01_orbit_inverse.py +0 -0
  71. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/scenarios/s01_passive_loco.py +0 -0
  72. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/scenarios/s01_perturbed_hill.py +0 -0
  73. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/scenarios/s01_sofb_observe_fit_act.py +0 -0
  74. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/scenarios/s01_streaming_archive_growth.py +0 -0
  75. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/scenarios/s01_synchrotron_ode.py +0 -0
  76. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/scenarios/s01_tides_3months.py +0 -0
  77. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/scenarios/s01_topoff_impulse.py +0 -0
  78. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/scenarios/s01_visualize.py +0 -0
  79. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/scenarios/s02_plasma_wakefield.py +0 -0
  80. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/scenarios/s03_synchrobetatron.py +0 -0
  81. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/scenarios/s04_sunspots.py +0 -0
  82. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/scenarios/s05_helioseismology.py +0 -0
  83. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/scenarios/s06_tides.py +0 -0
  84. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/scenarios/s07_iers_earth_rotation.py +0 -0
  85. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/scenarios/s08_mauna_loa_co2.py +0 -0
  86. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/scenarios/s09_enso_qbo.py +0 -0
  87. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/scenarios/s10_pulsar_timing.py +0 -0
  88. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/scenarios/s11_modal_analysis.py +0 -0
  89. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/scenarios/s12_mems_resonator.py +0 -0
  90. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/scenarios/s13_variable_stars_kepler.py +0 -0
  91. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/scenarios/s14_eeg.py +0 -0
  92. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/scenarios/s15_circadian.py +0 -0
  93. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/extras/spectral_expansion.py +0 -0
  94. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/grad_shafranov.py +0 -0
  95. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/grid_inverse.py +0 -0
  96. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/grid_rl_control.py +0 -0
  97. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/grid_swing.py +0 -0
  98. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/gs_inverse.py +0 -0
  99. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/gs_rl_control.py +0 -0
  100. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/inverse_heat_source.py +0 -0
  101. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/inverse_magnetostatics.py +0 -0
  102. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/inverse_source_position.py +0 -0
  103. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/learnable_helmholtz.py +0 -0
  104. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/orbit_hill.py +0 -0
  105. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/orbit_inverse.py +0 -0
  106. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/orbit_rl.py +0 -0
  107. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/pde_discovery.py +0 -0
  108. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/run_all_extensions.py +0 -0
  109. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/run_linear.py +0 -0
  110. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/run_nonlinear.py +0 -0
  111. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/tutorial_basic.py +0 -0
  112. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/tutorial_nonlinear.py +0 -0
  113. {fastlsq-0.2.4 → fastlsq-0.2.6}/examples/vector_basis_stream_vorticity.py +0 -0
  114. {fastlsq-0.2.4 → fastlsq-0.2.6}/fastlsq/api.py +0 -0
  115. {fastlsq-0.2.4 → fastlsq-0.2.6}/fastlsq/basis.py +0 -0
  116. {fastlsq-0.2.4 → fastlsq-0.2.6}/fastlsq/block.py +0 -0
  117. {fastlsq-0.2.4 → fastlsq-0.2.6}/fastlsq/device.py +0 -0
  118. {fastlsq-0.2.4 → fastlsq-0.2.6}/fastlsq/diagnostics.py +0 -0
  119. {fastlsq-0.2.4 → fastlsq-0.2.6}/fastlsq/export.py +0 -0
  120. {fastlsq-0.2.4 → fastlsq-0.2.6}/fastlsq/geometry.py +0 -0
  121. {fastlsq-0.2.4 → fastlsq-0.2.6}/fastlsq/learnable.py +0 -0
  122. {fastlsq-0.2.4 → fastlsq-0.2.6}/fastlsq/lightning.py +0 -0
  123. {fastlsq-0.2.4 → fastlsq-0.2.6}/fastlsq/newton.py +0 -0
  124. {fastlsq-0.2.4 → fastlsq-0.2.6}/fastlsq/plotting.py +0 -0
  125. {fastlsq-0.2.4 → fastlsq-0.2.6}/fastlsq/problems/__init__.py +0 -0
  126. {fastlsq-0.2.4 → fastlsq-0.2.6}/fastlsq/problems/nonlinear.py +0 -0
  127. {fastlsq-0.2.4 → fastlsq-0.2.6}/fastlsq/problems/regression.py +0 -0
  128. {fastlsq-0.2.4 → fastlsq-0.2.6}/fastlsq/solvers.py +0 -0
  129. {fastlsq-0.2.4 → fastlsq-0.2.6}/fastlsq/tuning.py +0 -0
  130. {fastlsq-0.2.4 → fastlsq-0.2.6}/fastlsq/utils.py +0 -0
  131. {fastlsq-0.2.4 → fastlsq-0.2.6}/fastlsq/vector.py +0 -0
  132. {fastlsq-0.2.4 → fastlsq-0.2.6}/fastlsq/viz.py +0 -0
  133. {fastlsq-0.2.4 → fastlsq-0.2.6}/requirements.txt +0 -0
  134. {fastlsq-0.2.4 → fastlsq-0.2.6}/setup.cfg +0 -0
  135. {fastlsq-0.2.4 → fastlsq-0.2.6}/tests/test_basic.py +0 -0
  136. {fastlsq-0.2.4 → fastlsq-0.2.6}/tests/test_block.py +0 -0
  137. {fastlsq-0.2.4 → fastlsq-0.2.6}/tests/test_derivatives.py +0 -0
  138. {fastlsq-0.2.4 → fastlsq-0.2.6}/tests/test_device.py +0 -0
  139. {fastlsq-0.2.4 → fastlsq-0.2.6}/tests/test_grad_shafranov.py +0 -0
  140. {fastlsq-0.2.4 → fastlsq-0.2.6}/tests/test_grid_swing.py +0 -0
  141. {fastlsq-0.2.4 → fastlsq-0.2.6}/tests/test_learnable.py +0 -0
  142. {fastlsq-0.2.4 → fastlsq-0.2.6}/tests/test_orbit_hill.py +0 -0
@@ -2,6 +2,35 @@
2
2
 
3
3
  All notable changes to FastLSQ will be documented in this file.
4
4
 
5
+ ## [0.2.5] - 2026-06-04
6
+
7
+ ### Fixed
8
+
9
+ - **`Wave2D_MS` solves via `solve_linear`.** The long-time anisotropic wave
10
+ returned relative value error 1.0 in every configuration because its
11
+ `t_max = 100` time normalisation packed ~87 temporal cycles into `tau ∈ [0,1]`:
12
+ the PDE's second time-derivative amplifies the random-feature *representation*
13
+ error by `Omega²` (`Omega = pi·sqrt(1+a2)·t_max`), so the one-shot
14
+ least-squares collocation cannot resolve the oscillation -- even 8000 features
15
+ with near-hard boundary constraints stay at rel-err 1.0, because the best
16
+ representable solution itself carries a huge PDE residual. Reducing `t_max` to
17
+ `4` (~3.5 cycles) and matching the anisotropic temporal feature bandwidth to
18
+ `Omega` (`scale_multipliers = [1, 1, 7]`) recovers the solution to ~3e-4 at
19
+ 900 features (`scale = 3`); the exactly-consistent `t_max²`-scaled operator is
20
+ unchanged. Added to the `tests/test_benchmarks_inverse.py` linear smoke test.
21
+ Resolves the `Wave2D_MS` [0.2.4] known issue.
22
+ - **`ElasticWave2D` solves via the block-stacked vector path.** The coupled
23
+ 2-output elastic-wave problem now declares `n_outputs = 2`, assembles its
24
+ operator in block-stacked form (`A ∈ ℝ^{Mk×Nk}`, `b ∈ ℝ^{Mk×1}`) via
25
+ `block_concat`, and gains the `exact_grad` Jacobian (shape `(M, d, k)`, time
26
+ axis chain-ruled by `t_max`) that the error metric requires. `unpack_beta` now
27
+ recovers a `(N, 2)` `beta`, so `solve_linear(ElasticWave2D(), scale=5.0)`
28
+ recovers both components (relative value error ~7e-3 at the default
29
+ resolution) instead of failing to unpack the vector solution. Added to the
30
+ `tests/test_benchmarks_inverse.py` linear smoke test. Resolves the
31
+ `ElasticWave2D` [0.2.4] known issue; the `t_max²` operator scaling from
32
+ [0.2.2] (consistent with `Wave2D_MS`) is preserved.
33
+
5
34
  ## [0.2.4] - 2026-06-04
6
35
 
7
36
  ### Added
@@ -18,10 +47,14 @@ All notable changes to FastLSQ will be documented in this file.
18
47
  ### Known issues
19
48
 
20
49
  - `Wave2D_MS` does not solve via `solve_linear` (relative error 1.0 in every
21
- configuration tested), and `ElasticWave2D` -- a 2-output vector problem whose
22
- `exact()` returns `(N, 2)` -- never sets `n_outputs`, so the scalar API cannot
23
- unpack it. Both are pre-existing problem-definition gaps, independent of the
24
- solver work, and are excluded from the new smoke test pending a fix.
50
+ configuration tested) -- a pre-existing problem-definition gap, independent of
51
+ the solver work, excluded from the new smoke test pending a fix. *(Fixed in
52
+ [0.2.5]: `t_max` reduced 100 -> 4 so the normalised-time oscillation
53
+ (~3.5 vs ~87 cycles) is resolvable; now covered by the smoke test.)*
54
+ - `ElasticWave2D` -- a 2-output vector problem whose `exact()` returns `(N, 2)`
55
+ -- never sets `n_outputs`, so the scalar API cannot unpack it; also excluded
56
+ here. *(Fixed in [0.2.5]: it now uses the block-stacked vector path and
57
+ is covered by the smoke test.)*
25
58
 
26
59
  ## [0.2.3] - 2026-06-04
27
60
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: FastLSQ
3
- Version: 0.2.4
3
+ Version: 0.2.6
4
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
@@ -55,9 +55,12 @@ analytical derivative engine for random Fourier features. For sinusoidal
55
55
  features `phi_j(x) = sin(W_j . x + b_j)`, every derivative of every order
56
56
  admits an exact closed-form expression -- no automatic differentiation needed.
57
57
 
58
- Linear PDEs are solved in a single least-squares step; nonlinear PDEs are
59
- solved via Newton-Raphson iteration with Tikhonov regularisation,
60
- 1/sqrt(N) feature normalisation, and continuation/homotopy.
58
+ Linear PDEs are solved in a single least-squares step. The random-feature
59
+ system is typically rank-deficient, so the solve is routed through a
60
+ backward-stable, auto-selected least-squares back-end (Cholesky fast-path ->
61
+ Householder QR -> rank-revealing SVD) that runs on CPU, CUDA, or Apple-MPS.
62
+ Nonlinear PDEs are solved via Newton-Raphson iteration with Tikhonov
63
+ regularisation, 1/sqrt(N) feature normalisation, and continuation/homotopy.
61
64
 
62
65
  ## Installation
63
66
 
@@ -68,7 +71,7 @@ pip install fastlsq
68
71
  For development (includes testing and build tools):
69
72
 
70
73
  ```bash
71
- git clone https://github.com/asulc/FastLSQ.git
74
+ git clone https://github.com/sulcantonin/FastLSQ.git
72
75
  cd FastLSQ
73
76
  pip install -e ".[dev]"
74
77
  ```
@@ -101,6 +104,26 @@ print(f"Converged in {result['n_iters']} iterations")
101
104
  print(f"Value error: {result['metrics']['val_err']:.2e}")
102
105
  ```
103
106
 
107
+ ### Choose a solver back-end and device
108
+
109
+ The linear solve is routed automatically, but `solve_linear` exposes the
110
+ back-end via `method=` (see [How it works](#how-it-works) for the routing):
111
+
112
+ ```python
113
+ from fastlsq import solve_linear, set_device
114
+ from fastlsq.problems.linear import PoissonND
115
+
116
+ # "auto" (default) -- Cholesky fast-path -> QR -> rank-revealing SVD
117
+ # "qr" -- Householder QR; SVD-grade accuracy at QR cost (full-rank A)
118
+ # "svd" -- rank-revealing truncated SVD; the rank-deficient-safe reference
119
+ # "cholesky" -- normal-equations Cholesky; fast, well-conditioned A only
120
+ # "rsvd" -- randomized SVD, O(MNk), for strongly low-rank A
121
+ result = solve_linear(PoissonND(), scale=5.0, method="qr")
122
+
123
+ # Device selection (CPU / CUDA / Apple-MPS), or set FASTLSQ_DEVICE=cuda
124
+ set_device("cuda") # the float64 default stays on CPU/CUDA; MPS is float32-only
125
+ ```
126
+
104
127
  ### Use the basis directly
105
128
 
106
129
  ```python
@@ -204,9 +227,10 @@ u_yy = A @ solver.beta # (M, k): ∂²u/∂y² per com
204
227
 
205
228
  Scalar problems are untouched: `n_outputs` defaults to `1`, `solver.beta` keeps
206
229
  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.
230
+ backward compatibility (the trailing component axis is squeezed when k=1). The
231
+ `Stokes2D` sketch above and [tests/test_block.py](tests/test_block.py) -- a
232
+ runnable `block_concat` + `unpack_beta` solve that recovers both components of a
233
+ k=2 system -- are the reference for the block-stacked vector path.
210
234
 
211
235
  ### Plot solutions
212
236
 
@@ -258,11 +282,15 @@ derivative engine:
258
282
  | `FastLSQSolver` | Manages feature blocks; exposes `.basis` for all derivative computations |
259
283
  | `LearnableFastLSQ` | Differentiable solver with learnable bandwidth via reparameterisation trick |
260
284
  | `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 |
285
+ | `solve_lstsq` | Multi-back-end least-squares solve (`auto`/`qr`/`svd`/`cholesky`/`rsvd`); rank-revealing by default for the rank-deficient feature matrix |
286
+ | `resolve_device` / `set_device` / `get_device` | CPU / CUDA / Apple-MPS selection, dtype-aware (MPS is float32-only; factorizations fall back to CPU) |
261
287
 
262
288
  ### How it works
263
289
 
264
290
  1. **Basis construction.** Given collocation points **x**, construct a
265
- `SinusoidalBasis` with random weights W and biases b.
291
+ `SinusoidalBasis` with random weights W and biases b. The collocation counts
292
+ default to scale with the feature count
293
+ (`n_pde = max(3000, 3 * n_blocks * hidden_size)`, `n_bc = max(800, n_pde // 5)`).
266
294
 
267
295
  2. **Analytical derivatives.** Exploit the cyclic derivative identity:
268
296
  the n-th derivative of sin(z) cycles through {sin, cos, -sin, -cos}
@@ -273,8 +301,13 @@ derivative engine:
273
301
  (e.g. `Op.laplacian(d=2)`) and apply it to the basis to get the system
274
302
  matrix `A`.
275
303
 
276
- 4. **Linear solve.** Solve `A beta = b` via least squares
277
- (optionally Tikhonov-regularised).
304
+ 4. **Linear solve.** Solve `A beta = b` in the least-squares sense. The
305
+ random-feature matrix `A` is typically rank-deficient (near-duplicate
306
+ columns), so the default `method="auto"` starts from a Cholesky fast-path
307
+ (guarded by a cheap conditioning probe), falls back to backward-stable
308
+ Householder **QR**, and resorts to a rank-revealing **SVD** only if the QR
309
+ solution blows up. A Tikhonov ridge `mu` enters via the `[A; sqrt(mu) I]`
310
+ augmentation, not the condition-squaring normal equations.
278
311
 
279
312
  5. **Newton iteration (nonlinear).** Linearise the PDE residual, solve
280
313
  `J delta_beta = -R` with backtracking line search, and repeat.
@@ -336,9 +369,12 @@ See `examples/add_your_own_pde.py` for the complete tutorial.
336
369
  - **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
370
  - **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
338
371
  - **High-level API**: Solve PDEs in one line with `solve_linear()` and `solve_nonlinear()`
372
+ - **Robust linear solver**: Pluggable least-squares back-ends; the default `auto` routes Cholesky -> QR -> SVD, and backward-stable QR delivers SVD-grade accuracy at QR cost on the rank-deficient random-feature system
339
373
  - **Learnable bandwidth**: `LearnableFastLSQ` optimises the bandwidth (scalar or anisotropic) via reparameterisation
340
374
  - **Learnable PDE coefficients**: Plug `nn.Parameter` into `Op` (e.g. Helmholtz wavenumber `k`) and optimise via AdamW; gradients flow through the prebuilt linear solve
341
375
  - **Auto-tuning**: Automatic scale selection via grid search
376
+ - **Device support**: CPU / CUDA / Apple-MPS via `set_device()` or the `FASTLSQ_DEVICE` env var, dtype-aware (the float64 high-accuracy path stays on CPU/CUDA)
377
+ - **Adaptive collocation**: `n_pde` / `n_bc` default to feature-count-scaled values, overridable per solve
342
378
  - **Built-in plotting**: Solution visualization, convergence plots, spectral sensitivity
343
379
  - **Geometry samplers**: Box, ball, sphere, interval, custom samplers
344
380
  - **Diagnostics**: Problem validation, conditioning checks, error detection
@@ -32,6 +32,17 @@ examples/run_nonlinear.py
32
32
  examples/tutorial_basic.py
33
33
  examples/tutorial_nonlinear.py
34
34
  examples/vector_basis_stream_vorticity.py
35
+ examples/digital_twins/darcy_heat.py
36
+ examples/digital_twins/pendulum.py
37
+ examples/digital_twins/pendulum_benchmark.py
38
+ examples/digital_twins/plasma_wakefield.py
39
+ examples/digital_twins/plasma_wakefield_2D_1.py
40
+ examples/digital_twins/plasma_wakefield_2D_2.py
41
+ examples/digital_twins/plasma_wakefield_2d_3.py
42
+ examples/digital_twins/plasma_wakefield_parameteric.py
43
+ examples/digital_twins/plot_utils.py
44
+ examples/digital_twins/structural_health_simple.py
45
+ examples/digital_twins/turbulence_gravity_cooling.py
35
46
  examples/extras/fred_sde.py
36
47
  examples/extras/fred_sde_fastlsq.py
37
48
  examples/extras/gaia_potential.py
@@ -73,6 +84,18 @@ examples/extras/scenarios/s12_mems_resonator.py
73
84
  examples/extras/scenarios/s13_variable_stars_kepler.py
74
85
  examples/extras/scenarios/s14_eeg.py
75
86
  examples/extras/scenarios/s15_circadian.py
87
+ examples/inverse/aero_.py
88
+ examples/inverse/denoising_parameter_estimation.py
89
+ examples/inverse/elastic_wave_animation.py
90
+ examples/inverse/heat_from_video.py
91
+ examples/inverse/inverse_turbulence.py
92
+ examples/inverse/shape_ns.py
93
+ examples/inverse/subsurface_imaging.py
94
+ examples/inverse/wing_optimize_simple.py
95
+ examples/sindy/compare_sindy_methods.py
96
+ examples/sindy/sindy_benchmarks.py
97
+ examples/sindy/sindy_differentiable.py
98
+ examples/sindy/sindy_minimal_diff.py
76
99
  fastlsq/__init__.py
77
100
  fastlsq/api.py
78
101
  fastlsq/basis.py
@@ -95,6 +118,16 @@ fastlsq/problems/__init__.py
95
118
  fastlsq/problems/linear.py
96
119
  fastlsq/problems/nonlinear.py
97
120
  fastlsq/problems/regression.py
121
+ misc/fastlsq_teaser.png
122
+ misc/ideal_quadrupole.png
123
+ misc/inverse_heat_source.gif
124
+ misc/inverse_heat_source.png
125
+ misc/inverse_magnetostatics.png
126
+ misc/inverse_magnetostatics_convergence.png
127
+ misc/quadrupole_convergence.png
128
+ misc/quadrupole_optimization.png
129
+ misc/tutorial_nlpoisson_convergence.png
130
+ misc/tutorial_nlpoisson_solution.png
98
131
  tests/test_basic.py
99
132
  tests/test_benchmarks_inverse.py
100
133
  tests/test_block.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: FastLSQ
3
- Version: 0.2.4
3
+ Version: 0.2.6
4
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
@@ -55,9 +55,12 @@ analytical derivative engine for random Fourier features. For sinusoidal
55
55
  features `phi_j(x) = sin(W_j . x + b_j)`, every derivative of every order
56
56
  admits an exact closed-form expression -- no automatic differentiation needed.
57
57
 
58
- Linear PDEs are solved in a single least-squares step; nonlinear PDEs are
59
- solved via Newton-Raphson iteration with Tikhonov regularisation,
60
- 1/sqrt(N) feature normalisation, and continuation/homotopy.
58
+ Linear PDEs are solved in a single least-squares step. The random-feature
59
+ system is typically rank-deficient, so the solve is routed through a
60
+ backward-stable, auto-selected least-squares back-end (Cholesky fast-path ->
61
+ Householder QR -> rank-revealing SVD) that runs on CPU, CUDA, or Apple-MPS.
62
+ Nonlinear PDEs are solved via Newton-Raphson iteration with Tikhonov
63
+ regularisation, 1/sqrt(N) feature normalisation, and continuation/homotopy.
61
64
 
62
65
  ## Installation
63
66
 
@@ -68,7 +71,7 @@ pip install fastlsq
68
71
  For development (includes testing and build tools):
69
72
 
70
73
  ```bash
71
- git clone https://github.com/asulc/FastLSQ.git
74
+ git clone https://github.com/sulcantonin/FastLSQ.git
72
75
  cd FastLSQ
73
76
  pip install -e ".[dev]"
74
77
  ```
@@ -101,6 +104,26 @@ print(f"Converged in {result['n_iters']} iterations")
101
104
  print(f"Value error: {result['metrics']['val_err']:.2e}")
102
105
  ```
103
106
 
107
+ ### Choose a solver back-end and device
108
+
109
+ The linear solve is routed automatically, but `solve_linear` exposes the
110
+ back-end via `method=` (see [How it works](#how-it-works) for the routing):
111
+
112
+ ```python
113
+ from fastlsq import solve_linear, set_device
114
+ from fastlsq.problems.linear import PoissonND
115
+
116
+ # "auto" (default) -- Cholesky fast-path -> QR -> rank-revealing SVD
117
+ # "qr" -- Householder QR; SVD-grade accuracy at QR cost (full-rank A)
118
+ # "svd" -- rank-revealing truncated SVD; the rank-deficient-safe reference
119
+ # "cholesky" -- normal-equations Cholesky; fast, well-conditioned A only
120
+ # "rsvd" -- randomized SVD, O(MNk), for strongly low-rank A
121
+ result = solve_linear(PoissonND(), scale=5.0, method="qr")
122
+
123
+ # Device selection (CPU / CUDA / Apple-MPS), or set FASTLSQ_DEVICE=cuda
124
+ set_device("cuda") # the float64 default stays on CPU/CUDA; MPS is float32-only
125
+ ```
126
+
104
127
  ### Use the basis directly
105
128
 
106
129
  ```python
@@ -204,9 +227,10 @@ u_yy = A @ solver.beta # (M, k): ∂²u/∂y² per com
204
227
 
205
228
  Scalar problems are untouched: `n_outputs` defaults to `1`, `solver.beta` keeps
206
229
  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.
230
+ backward compatibility (the trailing component axis is squeezed when k=1). The
231
+ `Stokes2D` sketch above and [tests/test_block.py](tests/test_block.py) -- a
232
+ runnable `block_concat` + `unpack_beta` solve that recovers both components of a
233
+ k=2 system -- are the reference for the block-stacked vector path.
210
234
 
211
235
  ### Plot solutions
212
236
 
@@ -258,11 +282,15 @@ derivative engine:
258
282
  | `FastLSQSolver` | Manages feature blocks; exposes `.basis` for all derivative computations |
259
283
  | `LearnableFastLSQ` | Differentiable solver with learnable bandwidth via reparameterisation trick |
260
284
  | `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 |
285
+ | `solve_lstsq` | Multi-back-end least-squares solve (`auto`/`qr`/`svd`/`cholesky`/`rsvd`); rank-revealing by default for the rank-deficient feature matrix |
286
+ | `resolve_device` / `set_device` / `get_device` | CPU / CUDA / Apple-MPS selection, dtype-aware (MPS is float32-only; factorizations fall back to CPU) |
261
287
 
262
288
  ### How it works
263
289
 
264
290
  1. **Basis construction.** Given collocation points **x**, construct a
265
- `SinusoidalBasis` with random weights W and biases b.
291
+ `SinusoidalBasis` with random weights W and biases b. The collocation counts
292
+ default to scale with the feature count
293
+ (`n_pde = max(3000, 3 * n_blocks * hidden_size)`, `n_bc = max(800, n_pde // 5)`).
266
294
 
267
295
  2. **Analytical derivatives.** Exploit the cyclic derivative identity:
268
296
  the n-th derivative of sin(z) cycles through {sin, cos, -sin, -cos}
@@ -273,8 +301,13 @@ derivative engine:
273
301
  (e.g. `Op.laplacian(d=2)`) and apply it to the basis to get the system
274
302
  matrix `A`.
275
303
 
276
- 4. **Linear solve.** Solve `A beta = b` via least squares
277
- (optionally Tikhonov-regularised).
304
+ 4. **Linear solve.** Solve `A beta = b` in the least-squares sense. The
305
+ random-feature matrix `A` is typically rank-deficient (near-duplicate
306
+ columns), so the default `method="auto"` starts from a Cholesky fast-path
307
+ (guarded by a cheap conditioning probe), falls back to backward-stable
308
+ Householder **QR**, and resorts to a rank-revealing **SVD** only if the QR
309
+ solution blows up. A Tikhonov ridge `mu` enters via the `[A; sqrt(mu) I]`
310
+ augmentation, not the condition-squaring normal equations.
278
311
 
279
312
  5. **Newton iteration (nonlinear).** Linearise the PDE residual, solve
280
313
  `J delta_beta = -R` with backtracking line search, and repeat.
@@ -336,9 +369,12 @@ See `examples/add_your_own_pde.py` for the complete tutorial.
336
369
  - **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
370
  - **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
338
371
  - **High-level API**: Solve PDEs in one line with `solve_linear()` and `solve_nonlinear()`
372
+ - **Robust linear solver**: Pluggable least-squares back-ends; the default `auto` routes Cholesky -> QR -> SVD, and backward-stable QR delivers SVD-grade accuracy at QR cost on the rank-deficient random-feature system
339
373
  - **Learnable bandwidth**: `LearnableFastLSQ` optimises the bandwidth (scalar or anisotropic) via reparameterisation
340
374
  - **Learnable PDE coefficients**: Plug `nn.Parameter` into `Op` (e.g. Helmholtz wavenumber `k`) and optimise via AdamW; gradients flow through the prebuilt linear solve
341
375
  - **Auto-tuning**: Automatic scale selection via grid search
376
+ - **Device support**: CPU / CUDA / Apple-MPS via `set_device()` or the `FASTLSQ_DEVICE` env var, dtype-aware (the float64 high-accuracy path stays on CPU/CUDA)
377
+ - **Adaptive collocation**: `n_pde` / `n_bc` default to feature-count-scaled values, overridable per solve
342
378
  - **Built-in plotting**: Solution visualization, convergence plots, spectral sensitivity
343
379
  - **Geometry samplers**: Box, ball, sphere, interval, custom samplers
344
380
  - **Diagnostics**: Problem validation, conditioning checks, error detection
@@ -14,9 +14,12 @@ analytical derivative engine for random Fourier features. For sinusoidal
14
14
  features `phi_j(x) = sin(W_j . x + b_j)`, every derivative of every order
15
15
  admits an exact closed-form expression -- no automatic differentiation needed.
16
16
 
17
- Linear PDEs are solved in a single least-squares step; nonlinear PDEs are
18
- solved via Newton-Raphson iteration with Tikhonov regularisation,
19
- 1/sqrt(N) feature normalisation, and continuation/homotopy.
17
+ Linear PDEs are solved in a single least-squares step. The random-feature
18
+ system is typically rank-deficient, so the solve is routed through a
19
+ backward-stable, auto-selected least-squares back-end (Cholesky fast-path ->
20
+ Householder QR -> rank-revealing SVD) that runs on CPU, CUDA, or Apple-MPS.
21
+ Nonlinear PDEs are solved via Newton-Raphson iteration with Tikhonov
22
+ regularisation, 1/sqrt(N) feature normalisation, and continuation/homotopy.
20
23
 
21
24
  ## Installation
22
25
 
@@ -27,7 +30,7 @@ pip install fastlsq
27
30
  For development (includes testing and build tools):
28
31
 
29
32
  ```bash
30
- git clone https://github.com/asulc/FastLSQ.git
33
+ git clone https://github.com/sulcantonin/FastLSQ.git
31
34
  cd FastLSQ
32
35
  pip install -e ".[dev]"
33
36
  ```
@@ -60,6 +63,26 @@ print(f"Converged in {result['n_iters']} iterations")
60
63
  print(f"Value error: {result['metrics']['val_err']:.2e}")
61
64
  ```
62
65
 
66
+ ### Choose a solver back-end and device
67
+
68
+ The linear solve is routed automatically, but `solve_linear` exposes the
69
+ back-end via `method=` (see [How it works](#how-it-works) for the routing):
70
+
71
+ ```python
72
+ from fastlsq import solve_linear, set_device
73
+ from fastlsq.problems.linear import PoissonND
74
+
75
+ # "auto" (default) -- Cholesky fast-path -> QR -> rank-revealing SVD
76
+ # "qr" -- Householder QR; SVD-grade accuracy at QR cost (full-rank A)
77
+ # "svd" -- rank-revealing truncated SVD; the rank-deficient-safe reference
78
+ # "cholesky" -- normal-equations Cholesky; fast, well-conditioned A only
79
+ # "rsvd" -- randomized SVD, O(MNk), for strongly low-rank A
80
+ result = solve_linear(PoissonND(), scale=5.0, method="qr")
81
+
82
+ # Device selection (CPU / CUDA / Apple-MPS), or set FASTLSQ_DEVICE=cuda
83
+ set_device("cuda") # the float64 default stays on CPU/CUDA; MPS is float32-only
84
+ ```
85
+
63
86
  ### Use the basis directly
64
87
 
65
88
  ```python
@@ -163,9 +186,10 @@ u_yy = A @ solver.beta # (M, k): ∂²u/∂y² per com
163
186
 
164
187
  Scalar problems are untouched: `n_outputs` defaults to `1`, `solver.beta` keeps
165
188
  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.
189
+ backward compatibility (the trailing component axis is squeezed when k=1). The
190
+ `Stokes2D` sketch above and [tests/test_block.py](tests/test_block.py) -- a
191
+ runnable `block_concat` + `unpack_beta` solve that recovers both components of a
192
+ k=2 system -- are the reference for the block-stacked vector path.
169
193
 
170
194
  ### Plot solutions
171
195
 
@@ -217,11 +241,15 @@ derivative engine:
217
241
  | `FastLSQSolver` | Manages feature blocks; exposes `.basis` for all derivative computations |
218
242
  | `LearnableFastLSQ` | Differentiable solver with learnable bandwidth via reparameterisation trick |
219
243
  | `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 |
244
+ | `solve_lstsq` | Multi-back-end least-squares solve (`auto`/`qr`/`svd`/`cholesky`/`rsvd`); rank-revealing by default for the rank-deficient feature matrix |
245
+ | `resolve_device` / `set_device` / `get_device` | CPU / CUDA / Apple-MPS selection, dtype-aware (MPS is float32-only; factorizations fall back to CPU) |
220
246
 
221
247
  ### How it works
222
248
 
223
249
  1. **Basis construction.** Given collocation points **x**, construct a
224
- `SinusoidalBasis` with random weights W and biases b.
250
+ `SinusoidalBasis` with random weights W and biases b. The collocation counts
251
+ default to scale with the feature count
252
+ (`n_pde = max(3000, 3 * n_blocks * hidden_size)`, `n_bc = max(800, n_pde // 5)`).
225
253
 
226
254
  2. **Analytical derivatives.** Exploit the cyclic derivative identity:
227
255
  the n-th derivative of sin(z) cycles through {sin, cos, -sin, -cos}
@@ -232,8 +260,13 @@ derivative engine:
232
260
  (e.g. `Op.laplacian(d=2)`) and apply it to the basis to get the system
233
261
  matrix `A`.
234
262
 
235
- 4. **Linear solve.** Solve `A beta = b` via least squares
236
- (optionally Tikhonov-regularised).
263
+ 4. **Linear solve.** Solve `A beta = b` in the least-squares sense. The
264
+ random-feature matrix `A` is typically rank-deficient (near-duplicate
265
+ columns), so the default `method="auto"` starts from a Cholesky fast-path
266
+ (guarded by a cheap conditioning probe), falls back to backward-stable
267
+ Householder **QR**, and resorts to a rank-revealing **SVD** only if the QR
268
+ solution blows up. A Tikhonov ridge `mu` enters via the `[A; sqrt(mu) I]`
269
+ augmentation, not the condition-squaring normal equations.
237
270
 
238
271
  5. **Newton iteration (nonlinear).** Linearise the PDE residual, solve
239
272
  `J delta_beta = -R` with backtracking line search, and repeat.
@@ -295,9 +328,12 @@ See `examples/add_your_own_pde.py` for the complete tutorial.
295
328
  - **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
329
  - **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
297
330
  - **High-level API**: Solve PDEs in one line with `solve_linear()` and `solve_nonlinear()`
331
+ - **Robust linear solver**: Pluggable least-squares back-ends; the default `auto` routes Cholesky -> QR -> SVD, and backward-stable QR delivers SVD-grade accuracy at QR cost on the rank-deficient random-feature system
298
332
  - **Learnable bandwidth**: `LearnableFastLSQ` optimises the bandwidth (scalar or anisotropic) via reparameterisation
299
333
  - **Learnable PDE coefficients**: Plug `nn.Parameter` into `Op` (e.g. Helmholtz wavenumber `k`) and optimise via AdamW; gradients flow through the prebuilt linear solve
300
334
  - **Auto-tuning**: Automatic scale selection via grid search
335
+ - **Device support**: CPU / CUDA / Apple-MPS via `set_device()` or the `FASTLSQ_DEVICE` env var, dtype-aware (the float64 high-accuracy path stays on CPU/CUDA)
336
+ - **Adaptive collocation**: `n_pde` / `n_bc` default to feature-count-scaled values, overridable per solve
301
337
  - **Built-in plotting**: Solution visualization, convergence plots, spectral sensitivity
302
338
  - **Geometry samplers**: Box, ball, sphere, interval, custom samplers
303
339
  - **Diagnostics**: Problem validation, conditioning checks, error detection