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