FastLSQ 0.1.5__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 (143) hide show
  1. {fastlsq-0.1.5 → fastlsq-0.2.1}/CHANGELOG.md +81 -20
  2. {fastlsq-0.1.5 → fastlsq-0.2.1/FastLSQ.egg-info}/PKG-INFO +4 -3
  3. fastlsq-0.2.1/FastLSQ.egg-info/SOURCES.txt +116 -0
  4. {fastlsq-0.1.5/FastLSQ.egg-info → fastlsq-0.2.1}/PKG-INFO +4 -3
  5. fastlsq-0.2.1/examples/extras/fred_sde.py +262 -0
  6. fastlsq-0.2.1/examples/extras/fred_sde_fastlsq.py +329 -0
  7. fastlsq-0.2.1/examples/extras/gaia_potential.py +183 -0
  8. fastlsq-0.2.1/examples/extras/gaia_potential_fastlsq.py +397 -0
  9. fastlsq-0.2.1/examples/extras/horizons_ephemeris.py +319 -0
  10. fastlsq-0.2.1/examples/extras/numerai_alpha.py +214 -0
  11. fastlsq-0.2.1/examples/extras/numerai_alpha_fastlsq.py +374 -0
  12. fastlsq-0.2.1/examples/extras/run_all_fastlsq.py +107 -0
  13. fastlsq-0.2.1/examples/extras/scenarios/__init__.py +0 -0
  14. fastlsq-0.2.1/examples/extras/scenarios/_alsu_lattice.py +196 -0
  15. fastlsq-0.2.1/examples/extras/scenarios/_common.py +164 -0
  16. fastlsq-0.2.1/examples/extras/scenarios/run_all.py +70 -0
  17. fastlsq-0.2.1/examples/extras/scenarios/s01_beamloss_ode.py +340 -0
  18. fastlsq-0.2.1/examples/extras/scenarios/s01_betatron_tune.py +448 -0
  19. fastlsq-0.2.1/examples/extras/scenarios/s01_green_fff.py +262 -0
  20. fastlsq-0.2.1/examples/extras/scenarios/s01_hill_ivp.py +263 -0
  21. fastlsq-0.2.1/examples/extras/scenarios/s01_observe_fit_act_simulator.py +353 -0
  22. fastlsq-0.2.1/examples/extras/scenarios/s01_orbit_inverse.py +332 -0
  23. fastlsq-0.2.1/examples/extras/scenarios/s01_passive_loco.py +246 -0
  24. fastlsq-0.2.1/examples/extras/scenarios/s01_perturbed_hill.py +247 -0
  25. fastlsq-0.2.1/examples/extras/scenarios/s01_sofb_observe_fit_act.py +619 -0
  26. fastlsq-0.2.1/examples/extras/scenarios/s01_streaming_archive_growth.py +310 -0
  27. fastlsq-0.2.1/examples/extras/scenarios/s01_synchrotron_ode.py +335 -0
  28. fastlsq-0.2.1/examples/extras/scenarios/s01_tides_3months.py +257 -0
  29. fastlsq-0.2.1/examples/extras/scenarios/s01_topoff_impulse.py +375 -0
  30. fastlsq-0.2.1/examples/extras/scenarios/s01_visualize.py +400 -0
  31. fastlsq-0.2.1/examples/extras/scenarios/s02_plasma_wakefield.py +99 -0
  32. fastlsq-0.2.1/examples/extras/scenarios/s03_synchrobetatron.py +75 -0
  33. fastlsq-0.2.1/examples/extras/scenarios/s04_sunspots.py +116 -0
  34. fastlsq-0.2.1/examples/extras/scenarios/s05_helioseismology.py +105 -0
  35. fastlsq-0.2.1/examples/extras/scenarios/s06_tides.py +122 -0
  36. fastlsq-0.2.1/examples/extras/scenarios/s07_iers_earth_rotation.py +97 -0
  37. fastlsq-0.2.1/examples/extras/scenarios/s08_mauna_loa_co2.py +96 -0
  38. fastlsq-0.2.1/examples/extras/scenarios/s09_enso_qbo.py +131 -0
  39. fastlsq-0.2.1/examples/extras/scenarios/s10_pulsar_timing.py +99 -0
  40. fastlsq-0.2.1/examples/extras/scenarios/s11_modal_analysis.py +109 -0
  41. fastlsq-0.2.1/examples/extras/scenarios/s12_mems_resonator.py +150 -0
  42. fastlsq-0.2.1/examples/extras/scenarios/s13_variable_stars_kepler.py +111 -0
  43. fastlsq-0.2.1/examples/extras/scenarios/s14_eeg.py +94 -0
  44. fastlsq-0.2.1/examples/extras/scenarios/s15_circadian.py +130 -0
  45. fastlsq-0.2.1/examples/extras/spectral_expansion.py +96 -0
  46. fastlsq-0.2.1/examples/grad_shafranov.py +186 -0
  47. fastlsq-0.2.1/examples/grid_inverse.py +74 -0
  48. fastlsq-0.2.1/examples/grid_rl_control.py +128 -0
  49. fastlsq-0.2.1/examples/grid_swing.py +130 -0
  50. fastlsq-0.2.1/examples/gs_inverse.py +127 -0
  51. fastlsq-0.2.1/examples/gs_rl_control.py +182 -0
  52. fastlsq-0.2.1/examples/orbit_hill.py +210 -0
  53. fastlsq-0.2.1/examples/orbit_inverse.py +98 -0
  54. fastlsq-0.2.1/examples/orbit_rl.py +154 -0
  55. fastlsq-0.2.1/examples/vector_basis_stream_vorticity.py +146 -0
  56. {fastlsq-0.1.5 → fastlsq-0.2.1}/fastlsq/__init__.py +12 -1
  57. {fastlsq-0.1.5 → fastlsq-0.2.1}/fastlsq/api.py +10 -5
  58. {fastlsq-0.1.5 → fastlsq-0.2.1}/fastlsq/basis.py +46 -9
  59. fastlsq-0.2.1/fastlsq/device.py +130 -0
  60. {fastlsq-0.1.5 → fastlsq-0.2.1}/fastlsq/geometry.py +16 -16
  61. {fastlsq-0.1.5 → fastlsq-0.2.1}/fastlsq/learnable.py +109 -68
  62. fastlsq-0.2.1/fastlsq/linalg.py +137 -0
  63. {fastlsq-0.1.5 → fastlsq-0.2.1}/fastlsq/newton.py +2 -2
  64. {fastlsq-0.1.5 → fastlsq-0.2.1}/fastlsq/problems/__init__.py +6 -4
  65. {fastlsq-0.1.5 → fastlsq-0.2.1}/fastlsq/problems/linear.py +29 -55
  66. {fastlsq-0.1.5 → fastlsq-0.2.1}/fastlsq/solvers.py +7 -7
  67. {fastlsq-0.1.5 → fastlsq-0.2.1}/fastlsq/tuning.py +3 -4
  68. {fastlsq-0.1.5 → fastlsq-0.2.1}/fastlsq/utils.py +8 -2
  69. fastlsq-0.2.1/fastlsq/vector.py +412 -0
  70. fastlsq-0.2.1/fastlsq/viz.py +456 -0
  71. {fastlsq-0.1.5 → fastlsq-0.2.1}/misc/inverse_heat_source.gif +0 -0
  72. {fastlsq-0.1.5 → fastlsq-0.2.1}/pyproject.toml +4 -3
  73. fastlsq-0.2.1/tests/test_block.py +68 -0
  74. fastlsq-0.2.1/tests/test_derivatives.py +209 -0
  75. fastlsq-0.2.1/tests/test_device.py +43 -0
  76. fastlsq-0.2.1/tests/test_grad_shafranov.py +41 -0
  77. fastlsq-0.2.1/tests/test_grid_swing.py +40 -0
  78. fastlsq-0.2.1/tests/test_learnable.py +98 -0
  79. fastlsq-0.2.1/tests/test_orbit_hill.py +42 -0
  80. fastlsq-0.2.1/tests/test_vector_basis.py +220 -0
  81. fastlsq-0.1.5/FastLSQ.egg-info/SOURCES.txt +0 -77
  82. fastlsq-0.1.5/examples/digital_twins/darcy_heat.py +0 -583
  83. fastlsq-0.1.5/examples/digital_twins/pendulum.py +0 -242
  84. fastlsq-0.1.5/examples/digital_twins/pendulum_benchmark.py +0 -281
  85. fastlsq-0.1.5/examples/digital_twins/plasma_wakefield.py +0 -339
  86. fastlsq-0.1.5/examples/digital_twins/plasma_wakefield_2D_1.py +0 -863
  87. fastlsq-0.1.5/examples/digital_twins/plasma_wakefield_2D_2.py +0 -769
  88. fastlsq-0.1.5/examples/digital_twins/plasma_wakefield_2d_3.py +0 -818
  89. fastlsq-0.1.5/examples/digital_twins/plasma_wakefield_parameteric.py +0 -475
  90. fastlsq-0.1.5/examples/digital_twins/plot_utils.py +0 -42
  91. fastlsq-0.1.5/examples/digital_twins/structural_health_simple.py +0 -343
  92. fastlsq-0.1.5/examples/digital_twins/turbulence_gravity_cooling.py +0 -387
  93. fastlsq-0.1.5/examples/inverse/aero_.py +0 -362
  94. fastlsq-0.1.5/examples/inverse/denoising_parameter_estimation.py +0 -297
  95. fastlsq-0.1.5/examples/inverse/elastic_wave_animation.py +0 -300
  96. fastlsq-0.1.5/examples/inverse/heat_from_video.py +0 -394
  97. fastlsq-0.1.5/examples/inverse/inverse_turbulence.py +0 -811
  98. fastlsq-0.1.5/examples/inverse/shape_ns.py +0 -1426
  99. fastlsq-0.1.5/examples/inverse/subsurface_imaging.py +0 -547
  100. fastlsq-0.1.5/examples/inverse/wing_optimize_simple.py +0 -283
  101. fastlsq-0.1.5/examples/sindy/compare_sindy_methods.py +0 -760
  102. fastlsq-0.1.5/examples/sindy/sindy_benchmarks.py +0 -351
  103. fastlsq-0.1.5/examples/sindy/sindy_differentiable.py +0 -418
  104. fastlsq-0.1.5/examples/sindy/sindy_minimal_diff.py +0 -716
  105. fastlsq-0.1.5/fastlsq/linalg.py +0 -34
  106. {fastlsq-0.1.5 → fastlsq-0.2.1}/FastLSQ.egg-info/dependency_links.txt +0 -0
  107. {fastlsq-0.1.5 → fastlsq-0.2.1}/FastLSQ.egg-info/requires.txt +0 -0
  108. {fastlsq-0.1.5 → fastlsq-0.2.1}/FastLSQ.egg-info/top_level.txt +0 -0
  109. {fastlsq-0.1.5 → fastlsq-0.2.1}/LICENSE +0 -0
  110. {fastlsq-0.1.5 → fastlsq-0.2.1}/MANIFEST.in +0 -0
  111. {fastlsq-0.1.5 → fastlsq-0.2.1}/README.md +0 -0
  112. {fastlsq-0.1.5 → fastlsq-0.2.1}/examples/add_your_own_pde.py +0 -0
  113. {fastlsq-0.1.5 → fastlsq-0.2.1}/examples/benchmark_comparison.py +0 -0
  114. {fastlsq-0.1.5 → fastlsq-0.2.1}/examples/custom_features.py +0 -0
  115. {fastlsq-0.1.5 → fastlsq-0.2.1}/examples/inverse_heat_source.py +0 -0
  116. {fastlsq-0.1.5 → fastlsq-0.2.1}/examples/inverse_magnetostatics.py +0 -0
  117. {fastlsq-0.1.5 → fastlsq-0.2.1}/examples/inverse_source_position.py +0 -0
  118. {fastlsq-0.1.5 → fastlsq-0.2.1}/examples/learnable_helmholtz.py +0 -0
  119. {fastlsq-0.1.5 → fastlsq-0.2.1}/examples/pde_discovery.py +0 -0
  120. {fastlsq-0.1.5 → fastlsq-0.2.1}/examples/run_all_extensions.py +0 -0
  121. {fastlsq-0.1.5 → fastlsq-0.2.1}/examples/run_linear.py +0 -0
  122. {fastlsq-0.1.5 → fastlsq-0.2.1}/examples/run_nonlinear.py +0 -0
  123. {fastlsq-0.1.5 → fastlsq-0.2.1}/examples/tutorial_basic.py +0 -0
  124. {fastlsq-0.1.5 → fastlsq-0.2.1}/examples/tutorial_nonlinear.py +0 -0
  125. {fastlsq-0.1.5 → fastlsq-0.2.1}/fastlsq/block.py +0 -0
  126. {fastlsq-0.1.5 → fastlsq-0.2.1}/fastlsq/diagnostics.py +0 -0
  127. {fastlsq-0.1.5 → fastlsq-0.2.1}/fastlsq/export.py +0 -0
  128. {fastlsq-0.1.5 → fastlsq-0.2.1}/fastlsq/lightning.py +0 -0
  129. {fastlsq-0.1.5 → fastlsq-0.2.1}/fastlsq/plotting.py +0 -0
  130. {fastlsq-0.1.5 → fastlsq-0.2.1}/fastlsq/problems/nonlinear.py +0 -0
  131. {fastlsq-0.1.5 → fastlsq-0.2.1}/fastlsq/problems/regression.py +0 -0
  132. {fastlsq-0.1.5 → fastlsq-0.2.1}/misc/fastlsq_teaser.png +0 -0
  133. {fastlsq-0.1.5 → fastlsq-0.2.1}/misc/ideal_quadrupole.png +0 -0
  134. {fastlsq-0.1.5 → fastlsq-0.2.1}/misc/inverse_heat_source.png +0 -0
  135. {fastlsq-0.1.5 → fastlsq-0.2.1}/misc/inverse_magnetostatics.png +0 -0
  136. {fastlsq-0.1.5 → fastlsq-0.2.1}/misc/inverse_magnetostatics_convergence.png +0 -0
  137. {fastlsq-0.1.5 → fastlsq-0.2.1}/misc/quadrupole_convergence.png +0 -0
  138. {fastlsq-0.1.5 → fastlsq-0.2.1}/misc/quadrupole_optimization.png +0 -0
  139. {fastlsq-0.1.5 → fastlsq-0.2.1}/misc/tutorial_nlpoisson_convergence.png +0 -0
  140. {fastlsq-0.1.5 → fastlsq-0.2.1}/misc/tutorial_nlpoisson_solution.png +0 -0
  141. {fastlsq-0.1.5 → fastlsq-0.2.1}/requirements.txt +0 -0
  142. {fastlsq-0.1.5 → fastlsq-0.2.1}/setup.cfg +0 -0
  143. {fastlsq-0.1.5 → fastlsq-0.2.1}/tests/test_basic.py +0 -0
@@ -2,29 +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
- - **Vector-valued solutions (`u: ℝᵈ → ℝᵏ`)**: first-class support for coupled
10
- systems and decoupled multi-output PDEs. Problems opt in with
11
- `self.n_outputs = k`; `solver.beta` has shape `(N, k)` and
12
- `solver.predict(x)` returns `(M, k)`. Scalar problems are the `k=1` case
13
- and remain bit-for-bit identical (Helmholtz 2D / Poisson 5D / Bratu 2D
14
- regressions verified).
15
- - New module `fastlsq.block`: `block_concat` assembles a 2-D nested list
16
- of `(M_i, N_j)` tensors into a block matrix (with `None` for zero
17
- blocks); `pack_beta` / `unpack_beta` convert between `(N, k)` and the
18
- block-stacked `(N*k, 1)` solve representation.
19
- - `predict_with_grad` / `predict_with_laplacian` einsum fixed to
20
- `"idh,hk->idk"` so the output dim no longer collapses for `k>1`. Squeezes
21
- back to `(M, d)` when `k=1` for backward compatibility.
22
- - `ElasticWave2D` refactored: gains `n_outputs = 2`, `build()` uses
23
- `block_concat`, and now exposes `exact_grad` with shape `(M, d, k)`.
24
- - `LearnableFastLSQ` accepts `n_outputs`; legacy subclasses that store a
25
- flat `(Nk, 1)` `beta` (e.g. `ElasticLearnable` in the elastic wave
26
- example) keep working under the default `n_outputs=1`.
27
- - **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"`).
28
89
 
29
90
  ## [0.2.0] - 2026-03-01
30
91
 
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: FastLSQ
3
- Version: 0.1.5
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,12 +14,13 @@ 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
@@ -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
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: FastLSQ
3
- Version: 0.1.5
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,12 +14,13 @@ 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
@@ -0,0 +1,262 @@
1
+ #!/usr/bin/env python
2
+ """Discover the drift and diffusion of an interest-rate SDE from data.
3
+
4
+ A short-rate model has the generic form
5
+ dr = mu(r,t) dt + sigma(r,t) dW.
6
+ The four classical templates:
7
+ Vasicek: mu = kappa(theta - r), sigma = sigma_0
8
+ CIR: mu = kappa(theta - r), sigma = sigma_0 sqrt(r)
9
+ CKLS: mu = kappa(theta - r), sigma = sigma_0 r^gamma
10
+ Hull-White: mu = (theta(t) - a r), sigma = sigma_0
11
+ For a fixed time-step approximation we have, increment by increment,
12
+ dr_i = mu(r_i) dt + sigma(r_i) sqrt(dt) z_i, z_i ~ N(0,1).
13
+ So given a series r_1,...,r_N we can estimate the conditional mean and
14
+ conditional variance of dr_i given r_i by binning, and fit a symbolic
15
+ mu(r) and sigma^2(r) to the binned estimates.
16
+
17
+ We try to download the FRED DGS10 series (10-year Treasury constant
18
+ maturity); if not available offline we fall back to synthetic Vasicek
19
+ or CIR data.
20
+
21
+ Usage: python fred_sde.py
22
+ """
23
+ from __future__ import annotations
24
+
25
+ import time
26
+ import io
27
+ import urllib.request
28
+ import numpy as np
29
+
30
+ np.set_printoptions(precision=4, suppress=True)
31
+
32
+
33
+ # ----------------------------------------------------------------------
34
+ # Data fetching (or synthetic fallback).
35
+ # ----------------------------------------------------------------------
36
+
37
+ def fetch_fred_dgs10(timeout=10):
38
+ """Try to download FRED DGS10 (10-year CMT) daily yields.
39
+ Returns (dates, values_in_decimal) or None if no internet."""
40
+ url = "https://fred.stlouisfed.org/graph/fredgraph.csv?id=DGS10"
41
+ try:
42
+ req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
43
+ with urllib.request.urlopen(req, timeout=timeout) as resp:
44
+ raw = resp.read().decode("utf-8")
45
+ except Exception as e:
46
+ print(f" FRED fetch failed ({type(e).__name__}: {e})", flush=True)
47
+ return None
48
+ # parse: DATE,DGS10 columns; skip header
49
+ dates, vals = [], []
50
+ for line in raw.splitlines()[1:]:
51
+ try:
52
+ d, v = line.split(",")
53
+ f = float(v)
54
+ dates.append(d); vals.append(f / 100.0) # percent → decimal
55
+ except (ValueError, IndexError):
56
+ continue
57
+ if len(vals) < 500:
58
+ return None
59
+ return np.array(dates), np.array(vals)
60
+
61
+
62
+ def synth_vasicek(n=3000, kappa=0.10, theta=0.045, sigma0=0.015, r0=0.04,
63
+ dt=1/252, seed=0):
64
+ rng = np.random.default_rng(seed)
65
+ r = np.empty(n); r[0] = r0
66
+ for i in range(1, n):
67
+ eps = rng.standard_normal()
68
+ r[i] = r[i-1] + kappa*(theta - r[i-1])*dt + sigma0*np.sqrt(dt)*eps
69
+ return r
70
+
71
+
72
+ def synth_cir(n=3000, kappa=0.08, theta=0.05, sigma0=0.07, r0=0.04,
73
+ dt=1/252, seed=0):
74
+ rng = np.random.default_rng(seed)
75
+ r = np.empty(n); r[0] = r0
76
+ for i in range(1, n):
77
+ eps = rng.standard_normal()
78
+ diff = sigma0 * np.sqrt(max(r[i-1], 1e-8)) * np.sqrt(dt) * eps
79
+ r[i] = max(r[i-1] + kappa*(theta - r[i-1])*dt + diff, 1e-8)
80
+ return r
81
+
82
+
83
+ # ----------------------------------------------------------------------
84
+ # Conditional moment estimation: bin r and compute E[dr|r], Var[dr|r].
85
+ # ----------------------------------------------------------------------
86
+
87
+ def conditional_moments(r, n_bins=20, dt=1/252):
88
+ """Return (r_bin_centres, mu_hat(r_bin), sigma2_hat(r_bin)) per bin."""
89
+ dr = np.diff(r)
90
+ r_at_step = r[:-1]
91
+ bins = np.quantile(r_at_step, np.linspace(0, 1, n_bins + 1))
92
+ bins[0] -= 1e-9; bins[-1] += 1e-9
93
+ idx = np.digitize(r_at_step, bins) - 1
94
+ r_c, m_c, v_c, n_c = [], [], [], []
95
+ for k in range(n_bins):
96
+ mask = (idx == k)
97
+ if mask.sum() < 5:
98
+ continue
99
+ r_c.append(r_at_step[mask].mean())
100
+ m_c.append(dr[mask].mean() / dt) # ≈ mu(r)
101
+ v_c.append(dr[mask].var() / dt) # ≈ sigma^2(r)
102
+ n_c.append(mask.sum())
103
+ return (np.asarray(r_c), np.asarray(m_c),
104
+ np.asarray(v_c), np.asarray(n_c))
105
+
106
+
107
+ # ----------------------------------------------------------------------
108
+ # Symbolic candidates for mu(r) and sigma^2(r).
109
+ # ----------------------------------------------------------------------
110
+
111
+ def fit_drift(r_c, mu_hat, n_c):
112
+ """Fit candidate drift forms; report best by AIC.
113
+ Candidates:
114
+ (Const) mu = a
115
+ (Linear-MR) mu = a + b*r (mean-reverting: b<0 means kappa>0)
116
+ (Quadratic) mu = a + b*r + c*r^2
117
+ """
118
+ w = np.sqrt(n_c)
119
+ cands = []
120
+ for name, basis_fn in [
121
+ ("Const", lambda r: np.stack([np.ones_like(r)], axis=1)),
122
+ ("Linear-MR", lambda r: np.stack([np.ones_like(r), r], axis=1)),
123
+ ("Quadratic", lambda r: np.stack([np.ones_like(r), r, r**2], axis=1)),
124
+ ]:
125
+ X = basis_fn(r_c)
126
+ Xw = X * w[:, None]; yw = mu_hat * w
127
+ coef, *_ = np.linalg.lstsq(Xw, yw, rcond=None)
128
+ resid = (X @ coef - mu_hat) * w
129
+ rss = float(resid @ resid)
130
+ k = X.shape[1]
131
+ aic = len(r_c) * np.log(max(rss / max(len(r_c), 1), 1e-30)) + 2 * k
132
+ cands.append((name, coef, rss, aic))
133
+ cands.sort(key=lambda x: x[3])
134
+ return cands
135
+
136
+
137
+ def fit_diffusion(r_c, sigma2_hat, n_c):
138
+ """Fit candidate sigma^2(r) forms; report best by AIC.
139
+ (Const) sigma^2 = a
140
+ (Linear-r) sigma^2 = a * r ← CIR (after rescaling)
141
+ (Linear-r^2) sigma^2 = a * r^2 ← geometric BM
142
+ (Power) sigma^2 = a * r^(2*gamma) — fit gamma in log space
143
+ """
144
+ w = np.sqrt(n_c)
145
+ cands = []
146
+ # Const, linear-r, linear-r^2 by weighted LSQ
147
+ for name, basis_fn in [
148
+ ("Const", lambda r: np.stack([np.ones_like(r)], axis=1)),
149
+ ("Linear-r", lambda r: np.stack([r], axis=1)),
150
+ ("Linear-r^2", lambda r: np.stack([r**2], axis=1)),
151
+ ]:
152
+ X = basis_fn(r_c)
153
+ Xw = X * w[:, None]; yw = sigma2_hat * w
154
+ coef, *_ = np.linalg.lstsq(Xw, yw, rcond=None)
155
+ resid = (X @ coef - sigma2_hat) * w
156
+ rss = float(resid @ resid)
157
+ k = X.shape[1]
158
+ aic = len(r_c) * np.log(max(rss / max(len(r_c), 1), 1e-30)) + 2 * k
159
+ cands.append((name, coef, rss, aic))
160
+ # Power: log(sigma^2) = log(a) + 2*gamma * log(r), needs r > 0
161
+ pos = r_c > 0
162
+ if pos.sum() > 3:
163
+ lx = np.log(r_c[pos]); ly = np.log(np.maximum(sigma2_hat[pos], 1e-20))
164
+ ww = w[pos]
165
+ X = np.stack([np.ones_like(lx), lx], axis=1)
166
+ Xw = X * ww[:, None]; yw = ly * ww
167
+ coef_p, *_ = np.linalg.lstsq(Xw, yw, rcond=None)
168
+ log_a, two_gamma = coef_p
169
+ # AIC in original space
170
+ pred = np.exp(log_a) * (r_c[pos] ** two_gamma)
171
+ resid = (pred - sigma2_hat[pos]) * w[pos]
172
+ rss = float(resid @ resid)
173
+ k = 2
174
+ aic = pos.sum() * np.log(max(rss / max(pos.sum(), 1), 1e-30)) + 2 * k
175
+ cands.append((f"Power(gamma={two_gamma/2:.2f})", coef_p, rss, aic))
176
+ cands.sort(key=lambda x: x[3])
177
+ return cands
178
+
179
+
180
+ # ----------------------------------------------------------------------
181
+ # Pretty-print one analysis
182
+ # ----------------------------------------------------------------------
183
+
184
+ def pretty_drift(name, coef):
185
+ if name == "Const":
186
+ return f"mu(r) = {coef[0]:+.4f}"
187
+ if name == "Linear-MR":
188
+ a, b = coef
189
+ # mean reversion: mu = a + b*r = b*(a/b + r), kappa = -b, theta = -a/b
190
+ kappa = -b
191
+ theta = -a / b if abs(b) > 1e-12 else float("nan")
192
+ return (f"mu(r) = {a:+.4f} + {b:+.4f} r "
193
+ f"(kappa={kappa:+.3f}, theta={theta:+.4f})")
194
+ if name == "Quadratic":
195
+ return (f"mu(r) = {coef[0]:+.4f} + {coef[1]:+.4f} r + {coef[2]:+.4f} r^2")
196
+ return f"{name}: coefs={coef}"
197
+
198
+
199
+ def pretty_diffusion(name, coef):
200
+ if name == "Const":
201
+ return f"sigma^2(r) = {coef[0]:.4g} => sigma = {np.sqrt(max(coef[0],0)):.4f}"
202
+ if name == "Linear-r":
203
+ return f"sigma^2(r) = {coef[0]:.4g} * r (CIR-like)"
204
+ if name == "Linear-r^2":
205
+ return f"sigma^2(r) = {coef[0]:.4g} * r^2 (geom-BM-like)"
206
+ if name.startswith("Power"):
207
+ log_a, two_gamma = coef
208
+ return (f"sigma^2(r) = {np.exp(log_a):.4g} * r^{two_gamma:.3f} "
209
+ f"({name})")
210
+ return f"{name}: coefs={coef}"
211
+
212
+
213
+ # ----------------------------------------------------------------------
214
+ # Driver
215
+ # ----------------------------------------------------------------------
216
+
217
+ def analyse(label, r, dt=1/252):
218
+ print(f"\n=== {label} (N={len(r)} observations)")
219
+ r_c, mu_hat, sig2_hat, n_c = conditional_moments(r, n_bins=20, dt=dt)
220
+ print(f" r range: {r_c.min():.4f} .. {r_c.max():.4f}")
221
+ print(f" binned drifts: range {mu_hat.min():+.4f} .. {mu_hat.max():+.4f}")
222
+ print(f" binned vars: range {sig2_hat.min():.4g} .. {sig2_hat.max():.4g}")
223
+ drift_cands = fit_drift(r_c, mu_hat, n_c)
224
+ diff_cands = fit_diffusion(r_c, sig2_hat, n_c)
225
+ print(" Drift candidates (sorted by AIC):")
226
+ for nm, coef, rss, aic in drift_cands:
227
+ marker = "←" if nm == drift_cands[0][0] else " "
228
+ print(f" {marker} {nm:12s} AIC={aic:9.1f} {pretty_drift(nm, coef)}")
229
+ print(" Diffusion candidates (sorted by AIC):")
230
+ for nm, coef, rss, aic in diff_cands:
231
+ marker = "←" if nm == diff_cands[0][0] else " "
232
+ print(f" {marker} {nm:20s} AIC={aic:9.1f} {pretty_diffusion(nm, coef)}")
233
+ return drift_cands[0], diff_cands[0]
234
+
235
+
236
+ def main():
237
+ print(">> Short-rate SDE discovery (FRED + synthetic checks)\n", flush=True)
238
+
239
+ # --- 1. Synthetic Vasicek
240
+ r_vas = synth_vasicek(n=3000, kappa=0.10, theta=0.045, sigma0=0.015, seed=0)
241
+ analyse("Synthetic Vasicek truth: drift Linear-MR (kappa=0.10, theta=0.045); "
242
+ "sigma^2 = const = 0.000225", r_vas)
243
+
244
+ # --- 2. Synthetic CIR
245
+ r_cir = synth_cir(n=3000, kappa=0.08, theta=0.05, sigma0=0.07, seed=0)
246
+ analyse("Synthetic CIR truth: drift Linear-MR (kappa=0.08, theta=0.050); "
247
+ "sigma^2 = 0.0049 * r", r_cir)
248
+
249
+ # --- 3. Real FRED data, if reachable
250
+ res = fetch_fred_dgs10()
251
+ if res is None:
252
+ print("\n=== FRED DGS10 unavailable (offline) --- skipping real-data run.")
253
+ else:
254
+ dates, r_real = res
255
+ # drop NaN-ish entries (FRED uses '.' for missing)
256
+ r_real = r_real[~np.isnan(r_real)]
257
+ analyse(f"FRED DGS10 (10-yr Treasury daily yields, {len(r_real)} obs)",
258
+ r_real)
259
+
260
+
261
+ if __name__ == "__main__":
262
+ main()