FastLSQ 0.4.2__tar.gz → 0.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fastlsq-0.4.2 → fastlsq-0.6.0}/CHANGELOG.md +256 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/FastLSQ.egg-info/PKG-INFO +132 -1
- {fastlsq-0.4.2 → fastlsq-0.6.0}/FastLSQ.egg-info/SOURCES.txt +11 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/PKG-INFO +132 -1
- {fastlsq-0.4.2 → fastlsq-0.6.0}/README.md +131 -0
- fastlsq-0.6.0/examples/integral_equations.py +93 -0
- fastlsq-0.6.0/examples/sdf_domains.py +126 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/fastlsq/__init__.py +54 -1
- fastlsq-0.6.0/fastlsq/augment.py +463 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/fastlsq/basis.py +465 -6
- fastlsq-0.6.0/fastlsq/geometry.py +1029 -0
- fastlsq-0.6.0/fastlsq/kernels.py +283 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/fastlsq/learnable.py +43 -17
- {fastlsq-0.4.2 → fastlsq-0.6.0}/fastlsq/problems/__init__.py +7 -0
- fastlsq-0.6.0/fastlsq/problems/integral.py +284 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/pyproject.toml +1 -1
- fastlsq-0.6.0/tests/test_augment.py +299 -0
- fastlsq-0.6.0/tests/test_geometry_sdf.py +314 -0
- fastlsq-0.6.0/tests/test_kernels.py +208 -0
- fastlsq-0.6.0/tests/test_multi_integral.py +146 -0
- fastlsq-0.6.0/tests/test_problems_integral.py +184 -0
- fastlsq-0.6.0/tests/test_symbol.py +273 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/tests/test_vector_basis.py +1 -1
- fastlsq-0.4.2/fastlsq/geometry.py +0 -243
- {fastlsq-0.4.2 → fastlsq-0.6.0}/FastLSQ.egg-info/dependency_links.txt +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/FastLSQ.egg-info/requires.txt +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/FastLSQ.egg-info/top_level.txt +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/LICENSE +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/MANIFEST.in +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/add_your_own_pde.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/benchmark_comparison.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/custom_features.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/fred_sde.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/fred_sde_fastlsq.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/gaia_potential.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/gaia_potential_fastlsq.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/horizons_ephemeris.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/numerai_alpha.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/numerai_alpha_fastlsq.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/run_all_fastlsq.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/scenarios/__init__.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/scenarios/_alsu_lattice.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/scenarios/_common.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/scenarios/run_all.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/scenarios/s01_beamloss_ode.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/scenarios/s01_betatron_tune.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/scenarios/s01_green_fff.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/scenarios/s01_hill_ivp.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/scenarios/s01_observe_fit_act_simulator.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/scenarios/s01_orbit_inverse.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/scenarios/s01_passive_loco.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/scenarios/s01_perturbed_hill.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/scenarios/s01_sofb_observe_fit_act.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/scenarios/s01_streaming_archive_growth.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/scenarios/s01_synchrotron_ode.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/scenarios/s01_tides_3months.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/scenarios/s01_topoff_impulse.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/scenarios/s01_visualize.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/scenarios/s02_plasma_wakefield.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/scenarios/s03_synchrobetatron.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/scenarios/s04_sunspots.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/scenarios/s05_helioseismology.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/scenarios/s06_tides.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/scenarios/s07_iers_earth_rotation.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/scenarios/s08_mauna_loa_co2.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/scenarios/s09_enso_qbo.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/scenarios/s10_pulsar_timing.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/scenarios/s11_modal_analysis.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/scenarios/s12_mems_resonator.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/scenarios/s13_variable_stars_kepler.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/scenarios/s14_eeg.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/scenarios/s15_circadian.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/extras/spectral_expansion.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/grad_shafranov.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/grid_inverse.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/grid_rl_control.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/grid_swing.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/gs_inverse.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/gs_rl_control.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/integro_differential_demo.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/inverse_heat_source.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/inverse_magnetostatics.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/inverse_source_position.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/learnable_helmholtz.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/memory_diffusion.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/orbit_hill.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/orbit_inverse.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/orbit_rl.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/pde_discovery.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/rlc_integro_differential.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/run_all_extensions.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/run_linear.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/run_nonlinear.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/tutorial_basic.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/tutorial_nonlinear.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/examples/vector_basis_stream_vorticity.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/fastlsq/api.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/fastlsq/benchmark.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/fastlsq/block.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/fastlsq/device.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/fastlsq/diagnostics.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/fastlsq/export.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/fastlsq/lightning.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/fastlsq/linalg.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/fastlsq/newton.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/fastlsq/plotting.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/fastlsq/problems/linear.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/fastlsq/problems/nonlinear.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/fastlsq/problems/regression.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/fastlsq/solvers.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/fastlsq/tuning.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/fastlsq/utils.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/fastlsq/vector.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/fastlsq/viz.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/requirements.txt +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/setup.cfg +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/tests/test_basic.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/tests/test_benchmarks_inverse.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/tests/test_block.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/tests/test_derivatives.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/tests/test_device.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/tests/test_diagnostics_timing.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/tests/test_grad_shafranov.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/tests/test_grid_swing.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/tests/test_integral.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/tests/test_learnable.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/tests/test_orbit_hill.py +0 -0
- {fastlsq-0.4.2 → fastlsq-0.6.0}/tests/test_projection.py +0 -0
|
@@ -2,6 +2,262 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to FastLSQ will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.6.0] - 2026-07-21
|
|
6
|
+
|
|
7
|
+
Completes the operator taxonomy: multi-axis integrals, separable kernels, and
|
|
8
|
+
Fredholm equations, with the integral-equation problems promoted into the same
|
|
9
|
+
`solve_linear` harness as the PDEs so they report in one table.
|
|
10
|
+
|
|
11
|
+
### New features
|
|
12
|
+
|
|
13
|
+
| Feature | API | What it buys |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| Multi-axis integrals | `MultiIntegralOperator`, `SinusoidalBasis.multi_integral` | Integrate over any subset of axes at once, each independently definite or Volterra — area/volume functionals and mixed "definite in space, running in time" memory terms that a single-axis operator cannot express |
|
|
16
|
+
| Separable (degenerate) kernels | `SeparableKernelOperator` | `K(x,y) = Σ g_m(x) h_m(y)` assembles as a rank-`R` product `G @ C`; the inner products `C` are computed once, independent of the collocation points |
|
|
17
|
+
| Fredholm, second kind | `fredholm_second_kind` | `u − λ∫K u = f` as `I − λK`, solved in the same single least squares as everything else |
|
|
18
|
+
| Kernel diagnostics | `degenerate_eigenvalues`, `check_quadrature` | The `λ` at which the equation is singular, and whether the inner-product quadrature actually resolves the basis — both failure modes that otherwise show up only as a bad fit |
|
|
19
|
+
| Integral Problem classes | `fastlsq.problems.integral` | Four integral / integro-differential problems with **closed-form** solutions, running through `solve_linear` |
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- **`MultiIntegralOperator` / `SinusoidalBasis.multi_integral`.** The plane wave
|
|
24
|
+
factorises over axes, `e^{iW·x} = ∏_k e^{iW_k x_k}`, so a multi-axis integral
|
|
25
|
+
is a *product* of the same numerically stable one-axis factors already used by
|
|
26
|
+
`definite_integral`, and the phase collapses to the value at the per-axis
|
|
27
|
+
midpoint:
|
|
28
|
+
|
|
29
|
+
∫∫ φ_j ∏_{k∈S} dx_k = [∏_{k∈S} Δ_k · sinc(W_k Δ_k / 2π)] · sin(Z_mid)
|
|
30
|
+
|
|
31
|
+
Exact, quadrature-free, and finite for near-DC features on every axis. With
|
|
32
|
+
`|S| = 1` it reproduces `IntegralOperator.definite` bit-for-bit (verified to
|
|
33
|
+
1e-14); against tensor-product quadrature in 2-D and 3-D it agrees to the
|
|
34
|
+
quadrature's own error. Order is 1 per axis — a repeated integral along one
|
|
35
|
+
axis remains `IntegralOperator`, whose Cauchy/Taylor branches handle the
|
|
36
|
+
small-`W` cancellation a naive repeated product would suffer.
|
|
37
|
+
|
|
38
|
+
- **`SeparableKernelOperator` (`fastlsq/kernels.py`).** A degenerate kernel
|
|
39
|
+
collapses the integral operator to `Σ_m g_m(x) ∫ h_m u`, so acting on the
|
|
40
|
+
basis needs only the `R × N` matrix `C_{mj} = ∫ h_m φ_j`. Assembly is
|
|
41
|
+
`G(x) @ C` — a rank-`R` factorisation instead of an `M × M` kernel evaluation,
|
|
42
|
+
and the assembled block's rank is exactly `R` (verified).
|
|
43
|
+
|
|
44
|
+
Quadrature enters in exactly one place, computing `C`, and it is a
|
|
45
|
+
precomputation rather than a per-row cost: tensor-product Gauss-Legendre,
|
|
46
|
+
spectrally convergent for smooth integrands. Because it must still resolve the
|
|
47
|
+
feature oscillation, `check_quadrature` reports the achieved convergence
|
|
48
|
+
(2e-14 for a typical basis; the test suite also pins that it *flags* an
|
|
49
|
+
under-resolved one), and `from_inner_products` accepts analytic `C` to skip
|
|
50
|
+
quadrature entirely.
|
|
51
|
+
|
|
52
|
+
- **`fredholm_second_kind` and `degenerate_eigenvalues`.** Fredholm equations of
|
|
53
|
+
the second kind assemble as `I − λK`. For a rank-`R` degenerate kernel there
|
|
54
|
+
are at most `R` characteristic values, at which the equation is singular;
|
|
55
|
+
`degenerate_eigenvalues` computes them so a near-singular `λ` is detectable
|
|
56
|
+
instead of silently producing a garbage fit. For `K(x,y)=xy` on `[0,1]` it
|
|
57
|
+
recovers the analytic `λ = 3` as `3.0000000000000093`.
|
|
58
|
+
|
|
59
|
+
Validated against degenerate-kernel theory rather than a reference quadrature:
|
|
60
|
+
the exact solution of `u − λ∫₀¹xy·u = f` is `u = f + λcx` with
|
|
61
|
+
`c = ∫yf/(1−λ/3)`, matched to ~1e-8 for `λ` swept from 0.25 to 2.9 (i.e. right
|
|
62
|
+
up to the singular value). `λ` may be an `nn.Parameter`.
|
|
63
|
+
|
|
64
|
+
- **`fastlsq/problems/integral.py`.** `FredholmProductKernel`,
|
|
65
|
+
`FredholmRank2Kernel`, `VolterraSecondKind` and `IntegroDifferentialODE`, each
|
|
66
|
+
with a closed-form solution, running through `solve_linear` exactly as the PDE
|
|
67
|
+
problems do — so integral equations now produce a results table alongside the
|
|
68
|
+
PDEs rather than living only in example scripts. The module docstring writes
|
|
69
|
+
down the (duck-typed, no base class) Problem contract, which was previously
|
|
70
|
+
implicit and inconsistent.
|
|
71
|
+
|
|
72
|
+
Note the second-kind problems return an **empty** `bcs` list: the identity term
|
|
73
|
+
makes them well posed with no boundary rows at all. Only the
|
|
74
|
+
integro-differential ODE, which has a genuine constant of integration, needs
|
|
75
|
+
one.
|
|
76
|
+
|
|
77
|
+
- **Accuracy regressions (`tests/test_problems_integral.py`).** Each problem is
|
|
78
|
+
solved through the real harness and checked against its analytic solution, and
|
|
79
|
+
separately each closed form is verified to satisfy its own equation by
|
|
80
|
+
independent quadrature — so a closed form and an operator cannot be wrong in
|
|
81
|
+
the same direction without being caught.
|
|
82
|
+
|
|
83
|
+
### Measured
|
|
84
|
+
|
|
85
|
+
All five integral-equation problems, through `solve_linear` (300 features, 2000
|
|
86
|
+
collocation points), against their closed-form solutions:
|
|
87
|
+
|
|
88
|
+
| Problem | rel L2 | grad rel L2 | boundary rows |
|
|
89
|
+
|---|---|---|---|
|
|
90
|
+
| Fredholm `K=xy`, λ=0.5 | 5.5e-14 | 5.7e-12 | 0 |
|
|
91
|
+
| Fredholm `K=xy`, λ=2.0 | 3.8e-13 | 3.9e-11 | 0 |
|
|
92
|
+
| Fredholm rank-2, λ=0.4 | 9.1e-14 | 9.5e-12 | 0 |
|
|
93
|
+
| Volterra 2nd kind, λ=1.5 | 8.8e-13 | 1.0e-10 | 0 |
|
|
94
|
+
| Integro-differential ODE, λ=4.0 | 6.2e-16 | 1.7e-14 | 1 |
|
|
95
|
+
|
|
96
|
+
Approaching the kernel's singular value `λ = 3` costs only ~2 orders of
|
|
97
|
+
magnitude: 5.5e-14 at `λ = 0.5`, 6.8e-13 at `λ = 2.9`, 3.9e-12 at `λ = 2.99`.
|
|
98
|
+
|
|
99
|
+
### Documentation
|
|
100
|
+
|
|
101
|
+
- The README now tables the SDF primitives (`sdf_ball`, `sdf_disk`, `sdf_box`,
|
|
102
|
+
`sdf_annulus`, `sdf_lshape`, `sdf_flower`, `sdf_polygon`, `sdf_tokamak`) and
|
|
103
|
+
the CSG combinators (`sdf_union`, `sdf_intersection`, `sdf_difference`,
|
|
104
|
+
`sdf_complement`) by name. They were exported in `__all__` from 0.5.0 but only
|
|
105
|
+
described in prose, so the composable `ψ`-level API was effectively
|
|
106
|
+
undiscoverable — you could find `SDFDomain.disk()` but not `sdf_disk`, and the
|
|
107
|
+
CSG functions not at all.
|
|
108
|
+
- `PolynomialColumns` is likewise named explicitly alongside `AugmentedBasis`.
|
|
109
|
+
|
|
110
|
+
### Changed
|
|
111
|
+
|
|
112
|
+
- **Operator composition is now duck-typed.** `IntegroDifferentialOperator._as_terms`
|
|
113
|
+
and `DiffOperator.__add__` dispatch on the presence of `.apply(basis, x, cache)`
|
|
114
|
+
rather than a fixed `isinstance` tuple, so operators defined in other modules
|
|
115
|
+
(`SeparableKernelOperator`, and any user-defined operator) compose with the
|
|
116
|
+
built-ins without `basis.py` importing them. No change to existing behaviour.
|
|
117
|
+
|
|
118
|
+
## [0.5.0] - 2026-07-21
|
|
119
|
+
|
|
120
|
+
Closes three gaps between what the paper describes and what the package shipped:
|
|
121
|
+
SDF geometry (§2.7), Fourier-symbol operators (§3.4), and explicit polynomial
|
|
122
|
+
augmentation columns (§2.3).
|
|
123
|
+
|
|
124
|
+
### Added
|
|
125
|
+
|
|
126
|
+
- **SDF / membership-oracle geometry (`fastlsq.geometry`).** A domain can now be
|
|
127
|
+
given as any callable `ψ(x)` that is negative inside — no mesh, no analytic
|
|
128
|
+
sampler. `sample_sdf` rejection-samples the interior (unbiased, so the radial
|
|
129
|
+
CDF of a disk is exactly `r²`), `project_to_boundary` lands points on `ψ = 0`,
|
|
130
|
+
and `outward_normal` returns `∇ψ/‖∇ψ‖` via autograd, which is what a Neumann or
|
|
131
|
+
Robin condition needs. `SDFDomain` bundles these with a bounding box and CSG
|
|
132
|
+
composition (`|`, `&`, `-`), plus `neumann_rows` / `robin_rows` that contract
|
|
133
|
+
the analytic basis gradient against the normal into an `(M, N)` block.
|
|
134
|
+
|
|
135
|
+
Shipped domains: `disk`, `ball`, `box`, `annulus` (multiply-connected),
|
|
136
|
+
`lshape` (reentrant corner), `flower` (smooth non-convex), `polygon`, and
|
|
137
|
+
`tokamak` — the D-shaped Miller poloidal cross-section, at the same MAST-U
|
|
138
|
+
scale (`R ∈ [0.6, 1.4]`) as `examples/grad_shafranov.py`, which until now used
|
|
139
|
+
a plain rectangle.
|
|
140
|
+
|
|
141
|
+
Projection is **damped** Newton, `x ← x − ψ∇ψ/‖∇ψ‖²`, with per-point
|
|
142
|
+
backtracking and monotone acceptance. The `1/‖∇ψ‖²` normalisation matters:
|
|
143
|
+
the textbook `x − ψ∇ψ` is valid only for a true distance function, and
|
|
144
|
+
`sdf_flower` has `‖∇ψ‖` spanning 1 to ~10. Undamped, it left
|
|
145
|
+
`|ψ| ≈ 1.5e-1` on the boundary; damped and monotone, 99.9% of interior seeds
|
|
146
|
+
converge to `1e-10`. The residual failures are seeds within `r < 0.2` of the
|
|
147
|
+
centre, where a non-distance `ψ` has no unique nearest boundary point;
|
|
148
|
+
`sample_boundary_sdf` filters them rather than hiding them.
|
|
149
|
+
|
|
150
|
+
- **`SymbolOperator` — Fourier-multiplier (nonlocal) operators.** Every feature
|
|
151
|
+
is a plane wave, so a multiplier `L e^{iξ·x} = m(ξ) e^{iξ·x}` acts *diagonally*
|
|
152
|
+
on the basis: assembling `L` is a per-column rescale, exact to machine
|
|
153
|
+
precision, with no quadrature and no discretisation of the kernel. Operators
|
|
154
|
+
that are hard for mesh methods — the fractional Laplacian is nonlocal with a
|
|
155
|
+
singular kernel, giving a dense ill-conditioned FEM/FD matrix — cost exactly
|
|
156
|
+
what the Laplacian costs here.
|
|
157
|
+
|
|
158
|
+
Factories: `fractional_laplacian(s)`, `riesz_potential(s)`,
|
|
159
|
+
`riesz_transform(k)`, `convolution(k̂)`. The order `s` may be an
|
|
160
|
+
`nn.Parameter`: the symbol is `exp(s·log‖ξ‖²)`, so gradient descent recovers a
|
|
161
|
+
planted order (test recovers `s = 0.65` to `<1e-3`). Composes with `Op` and
|
|
162
|
+
`IntegralOperator` through the usual arithmetic.
|
|
163
|
+
|
|
164
|
+
Verified against independent references, not just self-consistency: `s=1`
|
|
165
|
+
reproduces `−Δ` and `s=2` reproduces `Δ²` bit-exactly; the symbol is checked
|
|
166
|
+
against the **singular-integral definition** including its normalising constant
|
|
167
|
+
`C(1,s)`; and Gaussian convolution matches direct quadrature to `<1e-8`.
|
|
168
|
+
|
|
169
|
+
`ConvolutionOperator` is `SymbolOperator.convolution(k̂)` — this is what
|
|
170
|
+
`examples/memory_diffusion.py:80` was doing by hand with a `(1, N)` broadcast
|
|
171
|
+
coefficient. That hand-rolled version uses `basis.W[0:1, :]`, correct only
|
|
172
|
+
because `d=2` has exactly one spatial axis; a symbol sees all of `W` and
|
|
173
|
+
generalises to any dimension.
|
|
174
|
+
|
|
175
|
+
- **Polynomial / DC augmentation columns (`fastlsq.augment`).** `AugmentedBasis`
|
|
176
|
+
widens a basis with explicit `1, x, x², …` columns carrying **exact** operator
|
|
177
|
+
images — analytic monomial derivatives, antiderivatives (a negative multi-index
|
|
178
|
+
integrates: `x^p → x^{p+1}/(p+1)`), and Cauchy iterated integrals — not a
|
|
179
|
+
zero-derivative stub. It implements the same duck-typed protocol as
|
|
180
|
+
`SinusoidalBasis`, so `Op`, `IntegralOperator` and `SymbolOperator` all work
|
|
181
|
+
unchanged and return `(M, N + n_cols)`.
|
|
182
|
+
|
|
183
|
+
This is what §2.3 means by an integration constant "pinned by an explicit
|
|
184
|
+
polynomial column"; previously the only such column in the repo was hand-rolled
|
|
185
|
+
in `examples/extras/scenarios/s01_beamloss_ode.py`, which had to maintain its
|
|
186
|
+
derivative block by hand.
|
|
187
|
+
|
|
188
|
+
Scope, honestly: a sinusoidal bank with random phases can already approximate a
|
|
189
|
+
constant from near-DC features, so this is an improvement, not a rescue — on
|
|
190
|
+
`u'' = f` with a DC offset it is neutral at `C = 0` and ~12× better at
|
|
191
|
+
`C = 1000`. A non-constant monomial has no function-valued Fourier-multiplier
|
|
192
|
+
image, so `SymbolOperator` on `degree > 0` columns raises rather than inventing
|
|
193
|
+
a value.
|
|
194
|
+
|
|
195
|
+
### Fixed
|
|
196
|
+
|
|
197
|
+
- **`ProjectionOperator`'s docstring claimed the Fourier-symbol operators were
|
|
198
|
+
"already in the package".** They were not, until this release. It now points at
|
|
199
|
+
`SymbolOperator` and states the actual distinction (a symbol acts diagonally;
|
|
200
|
+
a projection row mixes features).
|
|
201
|
+
|
|
202
|
+
### Notes
|
|
203
|
+
|
|
204
|
+
- **Which fractional Laplacian.** The symbol calculus applies the multiplier to
|
|
205
|
+
the global plane-wave extension of the trial function, so
|
|
206
|
+
`fractional_laplacian` is the **whole-space (restricted)** `(−Δ)^s` on `R^d`,
|
|
207
|
+
*not* the spectral or regional variant defined by an eigenbasis of a bounded
|
|
208
|
+
domain. These coincide on `R^d` and differ on a bounded domain. Results should
|
|
209
|
+
not be read as the spectral fractional Laplacian.
|
|
210
|
+
|
|
211
|
+
- Boundary points from `sample_boundary_sdf` are **not** uniform with respect to
|
|
212
|
+
surface measure — they are the pushforward of the uniform box measure under the
|
|
213
|
+
projection, which over-weights convex bulges. Fine for collocation; do not use
|
|
214
|
+
them as quadrature nodes for a surface integral without reweighting.
|
|
215
|
+
|
|
216
|
+
## [0.4.3] - 2026-07-20
|
|
217
|
+
|
|
218
|
+
### Fixed
|
|
219
|
+
|
|
220
|
+
- **The Σ-learner trained uphill: `train_bandwidth` now uses the exact envelope
|
|
221
|
+
gradient.** The outer loss was backpropagated *through* `torch.linalg.lstsq`,
|
|
222
|
+
whose backward carries a `(AᵀA)⁻¹` and therefore squares `cond(A)`. For a
|
|
223
|
+
routine collocation system (`cond(A) ≈ 2.2e11` on the `AnisoPoisson` benchmark)
|
|
224
|
+
that needs ~5e22 of dynamic range, far past float64. Measured against central
|
|
225
|
+
finite differences at the isotropic init, the resulting "gradient" was **4.3e6×
|
|
226
|
+
too large with `cos(angle) = −0.707`** — an *ascent* direction, which `AdamW`
|
|
227
|
+
duly followed (`clip_grad` rescaled the magnitude but preserved the direction).
|
|
228
|
+
`fit()` consequently returned a solution ~3.6× *worse* than the isotropic
|
|
229
|
+
`solve_linear` baseline it is meant to beat.
|
|
230
|
+
|
|
231
|
+
The inner solve is now performed under `no_grad` and only the assembled `A(L)`
|
|
232
|
+
is differentiated. This is **exact, not an approximation**: for
|
|
233
|
+
`J(L) = ‖A(L)β* − b‖²` the chain-rule contribution through `β*` is
|
|
234
|
+
`2 (Aᵀr)ᵀ dβ*/dL`, and `Aᵀr = 0` at the least-squares optimum (the residual is
|
|
235
|
+
orthogonal to `range(A)`), so the term vanishes identically and
|
|
236
|
+
`dJ/dL = 2 rᵀ (dA/dL) β*` — the envelope (Danskin) theorem. The detached
|
|
237
|
+
gradient matches finite differences to **1.2e-3 relative, `cos(angle) = 1.0000`**,
|
|
238
|
+
and skips the ill-conditioned backward entirely.
|
|
239
|
+
|
|
240
|
+
On the `AnisoPoisson` benchmark the training loss now descends monotonically
|
|
241
|
+
(`1.9e-3` → `9.8e-11`) instead of climbing (`1.9e-3` → `1.2e+05`), an
|
|
242
|
+
independent held-out collocation draw tracks it the whole way (no overfitting),
|
|
243
|
+
and the learned Σ reaches `4.4e-8` against the isotropic `solve_linear`
|
|
244
|
+
baseline's `1.9e-4` — **~4350× better**, where it was previously ~3.6× *worse*.
|
|
245
|
+
Not backpropagating through the solve also makes each step ~6× cheaper
|
|
246
|
+
(`tests/test_learnable.py`: 389s → 57s).
|
|
247
|
+
|
|
248
|
+
Note this is the *third* attempt at this bug (see 0.2.1 and 0.2.2): both earlier
|
|
249
|
+
fixes swapped the inner solve's driver (`svd` → rank-revealing `gelsd`) to stop
|
|
250
|
+
the gradient being `NaN`. That made it **finite but never correct** — the loop
|
|
251
|
+
still differentiated through the solve. Differentiating through an
|
|
252
|
+
ill-conditioned least-squares solve is not stabilisable by choice of driver;
|
|
253
|
+
the envelope identity removes the need to do it at all.
|
|
254
|
+
- **`train_bandwidth` restored the wrong "best" iterate.** `best_params` was
|
|
255
|
+
snapshotted *after* `optimizer.step()`, so it stored `θ_{t+1}` while recording
|
|
256
|
+
`loss(θ_t)` — the best-iterate restore returned the successor of the best
|
|
257
|
+
iterate. The snapshot (and the `history` entry's `sigma` / `cov_diag`, which had
|
|
258
|
+
the same off-by-one) now happens before the step, so all three describe the same
|
|
259
|
+
point.
|
|
260
|
+
|
|
5
261
|
## [0.4.2] - 2026-07-20
|
|
6
262
|
|
|
7
263
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: FastLSQ
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
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
|
|
@@ -159,6 +159,128 @@ A_pde = helmholtz.apply(basis, x) # (5000, 1500)
|
|
|
159
159
|
wave = Op.partial(dim=2, order=2, d=3) - c**2 * Op.laplacian(d=3, dims=[0, 1])
|
|
160
160
|
```
|
|
161
161
|
|
|
162
|
+
### Nonlocal operators (fractional Laplacian, convolution)
|
|
163
|
+
|
|
164
|
+
Every feature is a plane wave, so a Fourier multiplier `m(ξ)` acts **diagonally**
|
|
165
|
+
on the basis -- assembling it is a per-column rescale, exact, with no quadrature
|
|
166
|
+
and no discretisation of the (singular, nonlocal) kernel:
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
from fastlsq.basis import SinusoidalBasis, SymbolOperator, Op
|
|
170
|
+
|
|
171
|
+
basis = SinusoidalBasis.random(input_dim=2, n_features=1500, sigma=5.0)
|
|
172
|
+
|
|
173
|
+
frac = SymbolOperator.fractional_laplacian(s=0.75) # (−Δ)^0.75
|
|
174
|
+
A = frac.apply(basis, x) # (M, 1500), one rescale
|
|
175
|
+
|
|
176
|
+
# Mixes freely with differential terms
|
|
177
|
+
L = SymbolOperator.fractional_laplacian(0.5) + 3.0 * Op.identity(d=2)
|
|
178
|
+
|
|
179
|
+
# Convolution from the kernel's transform; s may be an nn.Parameter, so the
|
|
180
|
+
# fractional order itself can be recovered by gradient descent.
|
|
181
|
+
K = SymbolOperator.convolution(lambda W: torch.exp(-(W**2).sum(0, keepdim=True) / 12))
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
`s=1` reproduces `−Δ` bit-exactly. Note this is the **whole-space (restricted)**
|
|
185
|
+
`(−Δ)^s`, not the spectral variant defined on a bounded domain -- the two differ
|
|
186
|
+
once the domain is bounded.
|
|
187
|
+
|
|
188
|
+
### Integral equations (Fredholm, Volterra, separable kernels)
|
|
189
|
+
|
|
190
|
+
A separable kernel `K(x,y) = Σ g_m(x) h_m(y)` collapses the integral operator to
|
|
191
|
+
`Σ_m g_m(x) ∫ h_m u`, so acting on the basis needs only an `R × N` matrix of
|
|
192
|
+
inner products, computed once. A Fredholm equation of the second kind is then
|
|
193
|
+
one linear least squares like everything else:
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
from fastlsq import SeparableKernelOperator, fredholm_second_kind, degenerate_eigenvalues
|
|
197
|
+
|
|
198
|
+
# u(x) − λ ∫₀¹ x y u(y) dy = f(x)
|
|
199
|
+
K = SeparableKernelOperator([lambda x: x[:, 0]], # g_m
|
|
200
|
+
[lambda y: y[:, 0]], # h_m
|
|
201
|
+
lower=0.0, upper=1.0, d=1)
|
|
202
|
+
|
|
203
|
+
print(degenerate_eigenvalues(K, basis)) # λ where the equation is singular → 3.0
|
|
204
|
+
print(K.check_quadrature(basis)) # are the inner products resolved?
|
|
205
|
+
|
|
206
|
+
L = fredholm_second_kind(K, lam=0.5, d=1)
|
|
207
|
+
beta = solve_lstsq(L.apply(basis, x), f(x))
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
Second-kind equations need **no boundary rows** — the identity term makes them
|
|
211
|
+
well posed on its own. Integration over several axes at once (definite, running,
|
|
212
|
+
or mixed) is `MultiIntegralOperator`:
|
|
213
|
+
|
|
214
|
+
```python
|
|
215
|
+
from fastlsq import MultiIntegralOperator
|
|
216
|
+
|
|
217
|
+
area = MultiIntegralOperator.definite([0, 1], [0, 0], [1, 1], d=2) # ∫∫ over a box
|
|
218
|
+
memory = MultiIntegralOperator([0, 1], [0.0, 0.0], d=2, uppers=[1.0, None]) # definite × running
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Ready-made problems with closed-form solutions live in `fastlsq.problems` and run
|
|
222
|
+
through `solve_linear` like the PDEs (`PYTHONPATH=. python3
|
|
223
|
+
examples/integral_equations.py`, 300 features, 2000 collocation points):
|
|
224
|
+
|
|
225
|
+
| Problem | rel L2 | grad rel L2 | boundary rows |
|
|
226
|
+
|---|---|---|---|
|
|
227
|
+
| `FredholmProductKernel(lam=0.5)` | 5.5e-14 | 5.7e-12 | 0 |
|
|
228
|
+
| `FredholmProductKernel(lam=2.0)` | 3.8e-13 | 3.9e-11 | 0 |
|
|
229
|
+
| `FredholmRank2Kernel(lam=0.4)` | 9.1e-14 | 9.5e-12 | 0 |
|
|
230
|
+
| `VolterraSecondKind(lam=1.5)` | 8.8e-13 | 1.0e-10 | 0 |
|
|
231
|
+
| `IntegroDifferentialODE(lam=4.0)` | 6.2e-16 | 1.7e-14 | 1 |
|
|
232
|
+
|
|
233
|
+
Errors are against the **closed-form** solutions (degenerate-kernel theory for
|
|
234
|
+
the Fredholm cases, the equivalent ODE for the Volterra ones), not a reference
|
|
235
|
+
quadrature. Accuracy degrades gracefully toward the kernel's singular value --
|
|
236
|
+
for `K = xy`, whose only characteristic value is `λ = 3`, the error moves from
|
|
237
|
+
5.5e-14 at `λ = 0.5` to 3.9e-12 at `λ = 2.99`.
|
|
238
|
+
|
|
239
|
+
### Complex geometry without a mesh
|
|
240
|
+
|
|
241
|
+
A domain is any callable that is negative inside. Interior points come from
|
|
242
|
+
rejection sampling, boundary points from projection onto `ψ = 0`, and outward
|
|
243
|
+
normals from `∇ψ/‖∇ψ‖` -- which is exactly what Neumann and Robin conditions need:
|
|
244
|
+
|
|
245
|
+
```python
|
|
246
|
+
from fastlsq.geometry import SDFDomain
|
|
247
|
+
|
|
248
|
+
dom = SDFDomain.annulus(0.3, 1.0) # or .disk() .lshape() .flower() .tokamak()
|
|
249
|
+
x = dom.sample(4000) # interior collocation
|
|
250
|
+
xb = dom.sample_boundary(600) # boundary collocation
|
|
251
|
+
B = dom.neumann_rows(basis, xb) # (M, N) block for ∂u/∂n = g
|
|
252
|
+
|
|
253
|
+
# Non-convex and multiply-connected domains are built, not meshed
|
|
254
|
+
plate = SDFDomain.disk(1.0) - SDFDomain.disk(0.2, center=(0.4, 0.0))
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Built-in domains, as `SDFDomain` constructors or as bare `ψ` callables:
|
|
258
|
+
|
|
259
|
+
| Domain | `SDFDomain` | Bare `ψ` | Why it's there |
|
|
260
|
+
|---|---|---|---|
|
|
261
|
+
| Ball / disk | `.ball()`, `.disk()` | `sdf_ball`, `sdf_disk` | Exact SDF, any dimension; the §2.7 unit disk |
|
|
262
|
+
| Axis-aligned box | `.box(lo, hi)` | `sdf_box` | Exact inside and out; the CSG building block |
|
|
263
|
+
| Annulus / shell | `.annulus(r_in, r_out)` | `sdf_annulus` | **Multiply-connected** — an interior boundary whose outward normal points toward the centre |
|
|
264
|
+
| L-shape | `.lshape(size, cut)` | `sdf_lshape` | **Reentrant corner**, the standard non-convex stress case (`r^{2/3}` solution singularity) |
|
|
265
|
+
| Flower | `.flower(R, a, k)` | `sdf_flower` | Smooth non-convex, and deliberately **not** a distance function (`‖∇ψ‖` spans 1–10) — the case that separates a correct projection from a naive one |
|
|
266
|
+
| Polygon | — | `sdf_polygon(verts)` | Exact for any simple polygon; the escape hatch for a cross-section known only as a curve (measured, CAD, traced) |
|
|
267
|
+
| Tokamak | `.tokamak()` | `sdf_tokamak` | D-shaped Miller poloidal cross-section, via `sdf_polygon` |
|
|
268
|
+
|
|
269
|
+
Any `ψ` of your own works too — it only has to be negative inside. Combine them
|
|
270
|
+
with the CSG helpers, which are also available as plain functions:
|
|
271
|
+
|
|
272
|
+
| Set operation | Operator | Function |
|
|
273
|
+
|---|---|---|
|
|
274
|
+
| Union `A ∪ B` | `A \| B` | `sdf_union(a, b)` |
|
|
275
|
+
| Intersection `A ∩ B` | `A & B` | `sdf_intersection(a, b)` |
|
|
276
|
+
| Difference `A \ B` | `A - B` | `sdf_difference(a, b)` |
|
|
277
|
+
| Complement | — | `sdf_complement(a)` |
|
|
278
|
+
|
|
279
|
+
CSG results are valid implicit functions (correct sign everywhere) but not
|
|
280
|
+
generally exact distance functions — `min`/`max` of two exact SDFs over- or
|
|
281
|
+
under-estimates distance near the seam. Nothing here depends on exactness:
|
|
282
|
+
sampling uses only the sign, and `project_to_boundary` normalises by `‖∇ψ‖²`.
|
|
283
|
+
|
|
162
284
|
### Vector-valued solutions
|
|
163
285
|
|
|
164
286
|
`solve_linear` / `solve_nonlinear` support vector-valued **u**: ℝᵈ → ℝᵏ for
|
|
@@ -279,7 +401,12 @@ derivative engine:
|
|
|
279
401
|
| `BasisCache` | Pre-computes sin(Z)/cos(Z) once, reuses across multiple derivative evaluations |
|
|
280
402
|
| `DiffOperator` / `Op` | Symbolic linear differential operators that compose via +, -, scalar *; coefficients can be `nn.Parameter` for learnable PDEs |
|
|
281
403
|
| `IntegralOperator` / `IntegroDifferentialOperator` | Closed-form **single-axis** definite / running (Volterra) integrals, including `order=n` **iterated** integrals `∫_lo^x (x−t)^{n−1}/(n−1)! φ dt`; compose with `Op` into one integro-differential design matrix |
|
|
404
|
+
| `MultiIntegralOperator` | Closed-form integration over **several axes at once**, each independently definite or Volterra -- area/volume functionals and mixed "definite in space, running in time" memory terms. The plane wave factorises over axes, so it is a product of the same stable one-axis factors |
|
|
405
|
+
| `SeparableKernelOperator` | Separable (degenerate) kernels `K(x,y) = Σ g_m(x) h_m(y)`, assembled as a rank-`R` product `G @ C` with the inner products `C` precomputed once. With `fredholm_second_kind` this makes `u − λ∫K u = f` one linear least squares |
|
|
406
|
+
| `SymbolOperator` | **Fourier-multiplier (nonlocal)** operators `L e^{iξ·x} = m(ξ) e^{iξ·x}`. Features *are* plane waves, so the symbol acts diagonally -- a per-column rescale, exact, no quadrature. Ships `fractional_laplacian(s)` (with learnable `s`), `riesz_potential`, `riesz_transform`, `convolution(k̂)` |
|
|
282
407
|
| `GaussianWindowedBasis` / `ProjectionOperator` | Windowed-Fourier (Gabor) basis + closed-form **projection (Radon)** operator `∫ f δ(c·z−u) dz` for tomographic / line-integral inverse problems; quadrature-free and differentiable in the optics `c` |
|
|
408
|
+
| `AugmentedBasis` / `PolynomialColumns` | Widens a basis with explicit `1, x, x², …` columns carrying **exact** operator images, to pin integration constants and DC modes that leave the sinusoidal family. Transparent to every operator |
|
|
409
|
+
| `SDFDomain` + `sample_sdf` / `project_to_boundary` / `outward_normal` | **Membership-oracle geometry**: give any `ψ(x)` negative inside and get interior points, boundary points and outward normals -- no mesh. CSG composition via `\|`, `&`, `-`; built-ins include disk, annulus, L-shape, flower, polygon and a tokamak cross-section |
|
|
283
410
|
| `FeatureBasis` | Adapter for non-sinusoidal solvers (e.g. PIELM with tanh) |
|
|
284
411
|
| `FastLSQSolver` | Manages feature blocks; exposes `.basis` for all derivative computations |
|
|
285
412
|
| `LearnableFastLSQ` | Differentiable solver with learnable bandwidth via reparameterisation trick |
|
|
@@ -370,7 +497,10 @@ See `examples/add_your_own_pde.py` for the complete tutorial.
|
|
|
370
497
|
- **Analytical derivative engine**: `SinusoidalBasis` computes arbitrary-order derivatives exactly in O(1) -- the foundation of the entire framework
|
|
371
498
|
- **Symbolic PDE operators**: Compose differential operators with `Op` (Laplacian, wave, Helmholtz, biharmonic, custom) via intuitive arithmetic; coefficients can be `nn.Parameter` for AdamW optimisation
|
|
372
499
|
- **Closed-form integral operators**: `IntegralOperator` (single-axis definite / Volterra integrals) composes with `Op` into one integro-differential least-squares block. The integral class now also includes the **projection (Radon) operator** (`ProjectionOperator` on a `GaussianWindowedBasis`) -- quadrature-free `∫ f δ(c·z−u) dz` line/hyperplane integrals for tomographic inverse problems, differentiable in the optics `c` for experiment design
|
|
500
|
+
- **Integral equations**: Separable (degenerate) kernels `K = Σ g_m(x) h_m(y)` assemble as a rank-`R` product with inner products precomputed once, so a Fredholm equation of the second kind `u − λ∫K u = f` is a single linear least squares needing **no boundary rows**. `degenerate_eigenvalues` reports the `λ` at which the equation is singular and `check_quadrature` whether the inner products are resolved -- both otherwise-silent failure modes. `MultiIntegralOperator` integrates over several axes at once, each independently definite or Volterra
|
|
501
|
+
- **Nonlocal / Fourier-symbol operators**: `SymbolOperator` assembles any multiplier `m(ξ)` as a per-column rescale -- exact, quadrature-free, and the same cost as the Laplacian. Covers the **fractional Laplacian** `(−Δ)^s` (with a *learnable* order `s`), Riesz potentials and transforms, and **convolution** `k * u` from the kernel transform `k̂`. Operators whose kernels are singular and nonlocal -- dense, ill-conditioned matrices for FEM/FD -- are diagonal here
|
|
373
502
|
- **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
|
|
503
|
+
- **Augmentation columns**: `AugmentedBasis` + `PolynomialColumns` widen the basis with exact `1, x, x², …` columns to pin integration constants and DC modes that leave the sinusoidal family -- transparent to every operator
|
|
374
504
|
- **High-level API**: Solve PDEs in one line with `solve_linear()` and `solve_nonlinear()`
|
|
375
505
|
- **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
|
|
376
506
|
- **Learnable bandwidth**: `LearnableFastLSQ` optimises the bandwidth (scalar or anisotropic) via reparameterisation
|
|
@@ -380,6 +510,7 @@ See `examples/add_your_own_pde.py` for the complete tutorial.
|
|
|
380
510
|
- **Adaptive collocation**: `n_pde` / `n_bc` default to feature-count-scaled values, overridable per solve
|
|
381
511
|
- **Built-in plotting**: Solution visualization, convergence plots, spectral sensitivity
|
|
382
512
|
- **Geometry samplers**: Box, ball, sphere, interval, custom samplers
|
|
513
|
+
- **Meshless complex geometry**: `SDFDomain` takes any membership oracle `ψ(x)` (negative inside) and supplies interior points, boundary points and outward normals `∇ψ/‖∇ψ‖` for Neumann/Robin conditions. CSG composition (`|`, `&`, `-`) builds non-convex and multiply-connected domains; built-ins include disk, annulus, L-shape, flower, arbitrary polygon, and a D-shaped tokamak poloidal cross-section
|
|
383
514
|
- **Diagnostics**: Problem validation, conditioning checks, error detection
|
|
384
515
|
- **Export utilities**: NumPy conversion, checkpoint saving/loading
|
|
385
516
|
- **PyTorch Lightning**: Integration for training loops
|
|
@@ -18,6 +18,7 @@ examples/grid_rl_control.py
|
|
|
18
18
|
examples/grid_swing.py
|
|
19
19
|
examples/gs_inverse.py
|
|
20
20
|
examples/gs_rl_control.py
|
|
21
|
+
examples/integral_equations.py
|
|
21
22
|
examples/integro_differential_demo.py
|
|
22
23
|
examples/inverse_heat_source.py
|
|
23
24
|
examples/inverse_magnetostatics.py
|
|
@@ -32,6 +33,7 @@ examples/rlc_integro_differential.py
|
|
|
32
33
|
examples/run_all_extensions.py
|
|
33
34
|
examples/run_linear.py
|
|
34
35
|
examples/run_nonlinear.py
|
|
36
|
+
examples/sdf_domains.py
|
|
35
37
|
examples/tutorial_basic.py
|
|
36
38
|
examples/tutorial_nonlinear.py
|
|
37
39
|
examples/vector_basis_stream_vorticity.py
|
|
@@ -78,6 +80,7 @@ examples/extras/scenarios/s14_eeg.py
|
|
|
78
80
|
examples/extras/scenarios/s15_circadian.py
|
|
79
81
|
fastlsq/__init__.py
|
|
80
82
|
fastlsq/api.py
|
|
83
|
+
fastlsq/augment.py
|
|
81
84
|
fastlsq/basis.py
|
|
82
85
|
fastlsq/benchmark.py
|
|
83
86
|
fastlsq/block.py
|
|
@@ -85,6 +88,7 @@ fastlsq/device.py
|
|
|
85
88
|
fastlsq/diagnostics.py
|
|
86
89
|
fastlsq/export.py
|
|
87
90
|
fastlsq/geometry.py
|
|
91
|
+
fastlsq/kernels.py
|
|
88
92
|
fastlsq/learnable.py
|
|
89
93
|
fastlsq/lightning.py
|
|
90
94
|
fastlsq/linalg.py
|
|
@@ -96,19 +100,26 @@ fastlsq/utils.py
|
|
|
96
100
|
fastlsq/vector.py
|
|
97
101
|
fastlsq/viz.py
|
|
98
102
|
fastlsq/problems/__init__.py
|
|
103
|
+
fastlsq/problems/integral.py
|
|
99
104
|
fastlsq/problems/linear.py
|
|
100
105
|
fastlsq/problems/nonlinear.py
|
|
101
106
|
fastlsq/problems/regression.py
|
|
107
|
+
tests/test_augment.py
|
|
102
108
|
tests/test_basic.py
|
|
103
109
|
tests/test_benchmarks_inverse.py
|
|
104
110
|
tests/test_block.py
|
|
105
111
|
tests/test_derivatives.py
|
|
106
112
|
tests/test_device.py
|
|
107
113
|
tests/test_diagnostics_timing.py
|
|
114
|
+
tests/test_geometry_sdf.py
|
|
108
115
|
tests/test_grad_shafranov.py
|
|
109
116
|
tests/test_grid_swing.py
|
|
110
117
|
tests/test_integral.py
|
|
118
|
+
tests/test_kernels.py
|
|
111
119
|
tests/test_learnable.py
|
|
120
|
+
tests/test_multi_integral.py
|
|
112
121
|
tests/test_orbit_hill.py
|
|
122
|
+
tests/test_problems_integral.py
|
|
113
123
|
tests/test_projection.py
|
|
124
|
+
tests/test_symbol.py
|
|
114
125
|
tests/test_vector_basis.py
|