FastLSQ 0.2.3__tar.gz → 0.2.4__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 (109) hide show
  1. {fastlsq-0.2.3 → fastlsq-0.2.4}/CHANGELOG.md +21 -0
  2. {fastlsq-0.2.3 → fastlsq-0.2.4}/FastLSQ.egg-info/PKG-INFO +1 -1
  3. {fastlsq-0.2.3 → fastlsq-0.2.4}/FastLSQ.egg-info/SOURCES.txt +1 -0
  4. {fastlsq-0.2.3 → fastlsq-0.2.4}/PKG-INFO +1 -1
  5. {fastlsq-0.2.3 → fastlsq-0.2.4}/fastlsq/__init__.py +1 -1
  6. {fastlsq-0.2.3 → fastlsq-0.2.4}/pyproject.toml +1 -1
  7. fastlsq-0.2.4/tests/test_benchmarks_inverse.py +144 -0
  8. {fastlsq-0.2.3 → fastlsq-0.2.4}/tests/test_vector_basis.py +1 -1
  9. {fastlsq-0.2.3 → fastlsq-0.2.4}/FastLSQ.egg-info/dependency_links.txt +0 -0
  10. {fastlsq-0.2.3 → fastlsq-0.2.4}/FastLSQ.egg-info/requires.txt +0 -0
  11. {fastlsq-0.2.3 → fastlsq-0.2.4}/FastLSQ.egg-info/top_level.txt +0 -0
  12. {fastlsq-0.2.3 → fastlsq-0.2.4}/LICENSE +0 -0
  13. {fastlsq-0.2.3 → fastlsq-0.2.4}/MANIFEST.in +0 -0
  14. {fastlsq-0.2.3 → fastlsq-0.2.4}/README.md +0 -0
  15. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/add_your_own_pde.py +0 -0
  16. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/benchmark_comparison.py +0 -0
  17. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/custom_features.py +0 -0
  18. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/fred_sde.py +0 -0
  19. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/fred_sde_fastlsq.py +0 -0
  20. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/gaia_potential.py +0 -0
  21. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/gaia_potential_fastlsq.py +0 -0
  22. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/horizons_ephemeris.py +0 -0
  23. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/numerai_alpha.py +0 -0
  24. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/numerai_alpha_fastlsq.py +0 -0
  25. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/run_all_fastlsq.py +0 -0
  26. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/scenarios/__init__.py +0 -0
  27. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/scenarios/_alsu_lattice.py +0 -0
  28. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/scenarios/_common.py +0 -0
  29. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/scenarios/run_all.py +0 -0
  30. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/scenarios/s01_beamloss_ode.py +0 -0
  31. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/scenarios/s01_betatron_tune.py +0 -0
  32. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/scenarios/s01_green_fff.py +0 -0
  33. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/scenarios/s01_hill_ivp.py +0 -0
  34. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/scenarios/s01_observe_fit_act_simulator.py +0 -0
  35. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/scenarios/s01_orbit_inverse.py +0 -0
  36. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/scenarios/s01_passive_loco.py +0 -0
  37. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/scenarios/s01_perturbed_hill.py +0 -0
  38. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/scenarios/s01_sofb_observe_fit_act.py +0 -0
  39. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/scenarios/s01_streaming_archive_growth.py +0 -0
  40. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/scenarios/s01_synchrotron_ode.py +0 -0
  41. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/scenarios/s01_tides_3months.py +0 -0
  42. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/scenarios/s01_topoff_impulse.py +0 -0
  43. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/scenarios/s01_visualize.py +0 -0
  44. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/scenarios/s02_plasma_wakefield.py +0 -0
  45. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/scenarios/s03_synchrobetatron.py +0 -0
  46. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/scenarios/s04_sunspots.py +0 -0
  47. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/scenarios/s05_helioseismology.py +0 -0
  48. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/scenarios/s06_tides.py +0 -0
  49. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/scenarios/s07_iers_earth_rotation.py +0 -0
  50. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/scenarios/s08_mauna_loa_co2.py +0 -0
  51. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/scenarios/s09_enso_qbo.py +0 -0
  52. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/scenarios/s10_pulsar_timing.py +0 -0
  53. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/scenarios/s11_modal_analysis.py +0 -0
  54. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/scenarios/s12_mems_resonator.py +0 -0
  55. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/scenarios/s13_variable_stars_kepler.py +0 -0
  56. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/scenarios/s14_eeg.py +0 -0
  57. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/scenarios/s15_circadian.py +0 -0
  58. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/extras/spectral_expansion.py +0 -0
  59. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/grad_shafranov.py +0 -0
  60. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/grid_inverse.py +0 -0
  61. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/grid_rl_control.py +0 -0
  62. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/grid_swing.py +0 -0
  63. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/gs_inverse.py +0 -0
  64. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/gs_rl_control.py +0 -0
  65. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/inverse_heat_source.py +0 -0
  66. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/inverse_magnetostatics.py +0 -0
  67. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/inverse_source_position.py +0 -0
  68. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/learnable_helmholtz.py +0 -0
  69. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/orbit_hill.py +0 -0
  70. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/orbit_inverse.py +0 -0
  71. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/orbit_rl.py +0 -0
  72. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/pde_discovery.py +0 -0
  73. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/run_all_extensions.py +0 -0
  74. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/run_linear.py +0 -0
  75. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/run_nonlinear.py +0 -0
  76. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/tutorial_basic.py +0 -0
  77. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/tutorial_nonlinear.py +0 -0
  78. {fastlsq-0.2.3 → fastlsq-0.2.4}/examples/vector_basis_stream_vorticity.py +0 -0
  79. {fastlsq-0.2.3 → fastlsq-0.2.4}/fastlsq/api.py +0 -0
  80. {fastlsq-0.2.3 → fastlsq-0.2.4}/fastlsq/basis.py +0 -0
  81. {fastlsq-0.2.3 → fastlsq-0.2.4}/fastlsq/block.py +0 -0
  82. {fastlsq-0.2.3 → fastlsq-0.2.4}/fastlsq/device.py +0 -0
  83. {fastlsq-0.2.3 → fastlsq-0.2.4}/fastlsq/diagnostics.py +0 -0
  84. {fastlsq-0.2.3 → fastlsq-0.2.4}/fastlsq/export.py +0 -0
  85. {fastlsq-0.2.3 → fastlsq-0.2.4}/fastlsq/geometry.py +0 -0
  86. {fastlsq-0.2.3 → fastlsq-0.2.4}/fastlsq/learnable.py +0 -0
  87. {fastlsq-0.2.3 → fastlsq-0.2.4}/fastlsq/lightning.py +0 -0
  88. {fastlsq-0.2.3 → fastlsq-0.2.4}/fastlsq/linalg.py +0 -0
  89. {fastlsq-0.2.3 → fastlsq-0.2.4}/fastlsq/newton.py +0 -0
  90. {fastlsq-0.2.3 → fastlsq-0.2.4}/fastlsq/plotting.py +0 -0
  91. {fastlsq-0.2.3 → fastlsq-0.2.4}/fastlsq/problems/__init__.py +0 -0
  92. {fastlsq-0.2.3 → fastlsq-0.2.4}/fastlsq/problems/linear.py +0 -0
  93. {fastlsq-0.2.3 → fastlsq-0.2.4}/fastlsq/problems/nonlinear.py +0 -0
  94. {fastlsq-0.2.3 → fastlsq-0.2.4}/fastlsq/problems/regression.py +0 -0
  95. {fastlsq-0.2.3 → fastlsq-0.2.4}/fastlsq/solvers.py +0 -0
  96. {fastlsq-0.2.3 → fastlsq-0.2.4}/fastlsq/tuning.py +0 -0
  97. {fastlsq-0.2.3 → fastlsq-0.2.4}/fastlsq/utils.py +0 -0
  98. {fastlsq-0.2.3 → fastlsq-0.2.4}/fastlsq/vector.py +0 -0
  99. {fastlsq-0.2.3 → fastlsq-0.2.4}/fastlsq/viz.py +0 -0
  100. {fastlsq-0.2.3 → fastlsq-0.2.4}/requirements.txt +0 -0
  101. {fastlsq-0.2.3 → fastlsq-0.2.4}/setup.cfg +0 -0
  102. {fastlsq-0.2.3 → fastlsq-0.2.4}/tests/test_basic.py +0 -0
  103. {fastlsq-0.2.3 → fastlsq-0.2.4}/tests/test_block.py +0 -0
  104. {fastlsq-0.2.3 → fastlsq-0.2.4}/tests/test_derivatives.py +0 -0
  105. {fastlsq-0.2.3 → fastlsq-0.2.4}/tests/test_device.py +0 -0
  106. {fastlsq-0.2.3 → fastlsq-0.2.4}/tests/test_grad_shafranov.py +0 -0
  107. {fastlsq-0.2.3 → fastlsq-0.2.4}/tests/test_grid_swing.py +0 -0
  108. {fastlsq-0.2.3 → fastlsq-0.2.4}/tests/test_learnable.py +0 -0
  109. {fastlsq-0.2.3 → fastlsq-0.2.4}/tests/test_orbit_hill.py +0 -0
@@ -2,6 +2,27 @@
2
2
 
3
3
  All notable changes to FastLSQ will be documented in this file.
4
4
 
5
+ ## [0.2.4] - 2026-06-04
6
+
7
+ ### Added
8
+
9
+ - **Benchmark + inverse-problem test suite** (`tests/test_benchmarks_inverse.py`):
10
+ 12 deterministic smoke tests (~11 s) that solve the linear (`PoissonND`,
11
+ `HeatND`, `Wave1D`, `Helmholtz2D`, `Maxwell2D_TM`) and nonlinear
12
+ (`NLPoisson2D`, `Bratu2D`, `SteadyBurgers1D`, `NLHelmholtz2D`, `AllenCahn1D`)
13
+ benchmark equations through the public `solve_linear` / `solve_nonlinear` API,
14
+ plus two inverse pipelines -- Gaussian source-position recovery (forward solve
15
+ + L-BFGS) and SINDy-style PDE discovery via analytical derivatives --
16
+ exercising the 0.2.3 QR / N-scaled-collocation solver path end to end.
17
+
18
+ ### Known issues
19
+
20
+ - `Wave2D_MS` does not solve via `solve_linear` (relative error 1.0 in every
21
+ configuration tested), and `ElasticWave2D` -- a 2-output vector problem whose
22
+ `exact()` returns `(N, 2)` -- never sets `n_outputs`, so the scalar API cannot
23
+ unpack it. Both are pre-existing problem-definition gaps, independent of the
24
+ solver work, and are excluded from the new smoke test pending a fix.
25
+
5
26
  ## [0.2.3] - 2026-06-04
6
27
 
7
28
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: FastLSQ
3
- Version: 0.2.3
3
+ Version: 0.2.4
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
@@ -96,6 +96,7 @@ fastlsq/problems/linear.py
96
96
  fastlsq/problems/nonlinear.py
97
97
  fastlsq/problems/regression.py
98
98
  tests/test_basic.py
99
+ tests/test_benchmarks_inverse.py
99
100
  tests/test_block.py
100
101
  tests/test_derivatives.py
101
102
  tests/test_device.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: FastLSQ
3
- Version: 0.2.3
3
+ Version: 0.2.4
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
@@ -44,7 +44,7 @@ from fastlsq.export import (
44
44
  )
45
45
  from fastlsq import viz
46
46
 
47
- __version__ = "0.2.3"
47
+ __version__ = "0.2.4"
48
48
  __all__ = [
49
49
  # Device selection (CPU / CUDA / Apple-MPS, dtype-aware)
50
50
  "resolve_device",
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "FastLSQ"
7
- version = "0.2.3"
7
+ version = "0.2.4"
8
8
  description = "One-shot PDE solving via Fourier features with exact analytical derivatives; rank-revealing solvers, learnable anisotropic bandwidth, and CPU/CUDA/MPS support"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -0,0 +1,144 @@
1
+ # Copyright (c) 2026 Antonin Sulc -- MIT.
2
+ """Smoke tests for the benchmark PDE equations and the inverse-problem workflows.
3
+
4
+ Exercises the forward benchmark problems through the public ``solve_linear`` /
5
+ ``solve_nonlinear`` API and two inverse pipelines -- parameter recovery via an
6
+ outer optimiser, and SINDy-style PDE discovery via analytical derivatives -- so
7
+ the v0.2.3 QR / N-scaled-collocation solver path is covered end-to-end, not just
8
+ on the single Poisson problem in ``test_basic``.
9
+
10
+ Scales are fixed (not auto-selected) and the RNG is seeded so the smoke test is
11
+ fast and deterministic; tolerances carry ~10x headroom over measured errors.
12
+
13
+ Excluded (pre-existing, unrelated to the solver work):
14
+ * ``Wave2D_MS`` -- rel-err == 1.0 via ``solve_linear`` in every config
15
+ (old 10000/2000 defaults included), i.e. does not solve.
16
+ * ``ElasticWave2D``-- a 2-output vector problem (``exact()`` returns (N, 2))
17
+ that never sets ``n_outputs``, so the scalar API can't
18
+ unpack it. Needs the vector solver path.
19
+ """
20
+ import numpy as np
21
+ import pytest
22
+ import torch
23
+
24
+ from fastlsq import (
25
+ solve_linear, solve_nonlinear, solve_lstsq, Op, SinusoidalBasis,
26
+ sample_box, sample_boundary_box,
27
+ )
28
+ from fastlsq.problems import linear as L
29
+ from fastlsq.problems import nonlinear as NL
30
+
31
+
32
+ # (class, fixed scale, val_err tolerance)
33
+ LINEAR_CASES = [
34
+ (L.PoissonND, 0.5, 5e-3),
35
+ (L.HeatND, 0.5, 1e-1),
36
+ (L.Wave1D, 15.0, 5e-3),
37
+ (L.Helmholtz2D, 10.0, 1e-5),
38
+ (L.Maxwell2D_TM, 2.0, 5e-3),
39
+ ]
40
+
41
+ NONLINEAR_CASES = [
42
+ (NL.NLPoisson2D, 8.0, 1e-4),
43
+ (NL.Bratu2D, 15.0, 1e-4),
44
+ (NL.SteadyBurgers1D,10.0, 1e-4),
45
+ (NL.NLHelmholtz2D, 5.0, 1e-4),
46
+ (NL.AllenCahn1D, 15.0, 2e-1),
47
+ ]
48
+
49
+
50
+ @pytest.mark.parametrize(
51
+ "cls,scale,tol", LINEAR_CASES, ids=[c[0].__name__ for c in LINEAR_CASES]
52
+ )
53
+ def test_linear_benchmark_solves(cls, scale, tol):
54
+ """Each linear benchmark equation solves end-to-end via the public API."""
55
+ torch.set_default_dtype(torch.float64)
56
+ torch.manual_seed(0)
57
+ r = solve_linear(cls(), scale=scale, n_blocks=2, hidden_size=300,
58
+ n_test=1500, auto_scale=False, verbose=False)
59
+ ve = r["metrics"]["val_err"]
60
+ assert np.isfinite(ve), f"{cls.__name__}: non-finite val_err"
61
+ assert ve < tol, f"{cls.__name__}: val_err={ve:.2e} exceeds tol {tol:.0e}"
62
+
63
+
64
+ @pytest.mark.parametrize(
65
+ "cls,scale,tol", NONLINEAR_CASES, ids=[c[0].__name__ for c in NONLINEAR_CASES]
66
+ )
67
+ def test_nonlinear_benchmark_solves(cls, scale, tol):
68
+ """Each nonlinear benchmark equation converges via Newton + the public API."""
69
+ torch.set_default_dtype(torch.float64)
70
+ torch.manual_seed(0)
71
+ r = solve_nonlinear(cls(), scale=scale, n_blocks=2, hidden_size=300,
72
+ n_test=1500, max_iter=15, auto_scale=False, verbose=False)
73
+ ve = r["metrics"]["val_err"]
74
+ assert r["n_iters"] > 0, f"{cls.__name__}: no Newton iterations ran"
75
+ assert np.isfinite(ve), f"{cls.__name__}: non-finite val_err"
76
+ assert ve < tol, f"{cls.__name__}: val_err={ve:.2e} exceeds tol {tol:.0e}"
77
+
78
+
79
+ def test_inverse_source_position():
80
+ """Recover a Gaussian source position from sensor data (forward solve + L-BFGS)."""
81
+ opt = pytest.importorskip("scipy.optimize")
82
+ torch.set_default_dtype(torch.float64)
83
+ torch.manual_seed(0)
84
+
85
+ pde_op = -Op.laplacian(d=2)
86
+ basis = SinusoidalBasis.random(input_dim=2, n_features=700, sigma=5.0,
87
+ normalize=True)
88
+ x_pde = sample_box(3000, 2)
89
+ x_bc = sample_boundary_box(400, 2)
90
+ n_bc = x_bc.shape[0]
91
+ cache = basis.cache(x_pde)
92
+ A = torch.cat([pde_op.apply(basis, x_pde, cache=cache),
93
+ 100.0 * basis.evaluate(x_bc)])
94
+ x_sens = torch.tensor([[0.3, 0.3], [0.7, 0.7], [0.3, 0.7], [0.7, 0.3]])
95
+
96
+ def forward(xs, ys):
97
+ b = torch.exp(-((x_pde[:, 0] - xs) ** 2
98
+ + (x_pde[:, 1] - ys) ** 2) / 0.1).unsqueeze(1)
99
+ b = torch.cat([b, torch.zeros(n_bc, 1, dtype=b.dtype)])
100
+ beta = solve_lstsq(A, b)
101
+ return (basis.evaluate(x_sens) @ beta).detach().cpu().numpy().ravel()
102
+
103
+ true = np.array([0.4, 0.6])
104
+ rng = np.random.default_rng(0)
105
+ u_obs = forward(*true) + 0.005 * rng.standard_normal(4)
106
+
107
+ res = opt.minimize(
108
+ lambda p: float(np.sum((forward(float(p[0]), float(p[1])) - u_obs) ** 2)),
109
+ x0=[0.5, 0.5], method="L-BFGS-B", bounds=[(0.1, 0.9)] * 2,
110
+ )
111
+ assert np.linalg.norm(res.x - true) < 0.06, f"recovered {res.x} vs {true}"
112
+
113
+
114
+ def test_pde_discovery_recovers_governing_equation():
115
+ """SINDy-style discovery via analytical derivatives recovers u_xx = a*u + b*u_x.
116
+
117
+ Synthetic damped oscillator u = exp(-x/2) sin(2x) -> u_xx = -4.25 u - 1.0 u_x.
118
+ The dominant restoring term is recovered tightly; the damping term is harder
119
+ from 2% noise, so it is only bounded in sign/magnitude.
120
+ """
121
+ torch.set_default_dtype(torch.float64)
122
+ torch.manual_seed(42)
123
+
124
+ M = 500
125
+ x = torch.linspace(0, 2 * np.pi, M).reshape(-1, 1)
126
+ u_true = torch.exp(-0.5 * x) * torch.sin(2 * x)
127
+ u_noisy = u_true + 0.02 * torch.randn_like(u_true)
128
+
129
+ basis = SinusoidalBasis.random(input_dim=1, n_features=400, sigma=4.0,
130
+ normalize=True)
131
+ beta = solve_lstsq(basis.evaluate(x), u_noisy, mu=1e-3)
132
+ cache = basis.cache(x)
133
+ u = basis.evaluate(x, cache=cache) @ beta
134
+ u_x = basis.derivative(x, alpha=(1,), cache=cache) @ beta
135
+ u_xx = basis.derivative(x, alpha=(2,), cache=cache) @ beta
136
+
137
+ coef = solve_lstsq(torch.cat([u, u_x], dim=1), u_xx) # u_xx = a*u + b*u_x
138
+ a, b = float(coef[0]), float(coef[1])
139
+ assert abs(a - (-4.25)) < 0.3, f"restoring coeff a={a:.3f} (want -4.25)"
140
+ assert b < 0 and abs(b - (-1.0)) < 0.5, f"damping coeff b={b:.3f} (want -1.0)"
141
+
142
+
143
+ if __name__ == "__main__":
144
+ pytest.main([__file__, "-v"])
@@ -20,7 +20,7 @@ from fastlsq.utils import device
20
20
  # ----------------------------------------------------------------------
21
21
 
22
22
  def test_version():
23
- assert fastlsq.__version__ == "0.2.3"
23
+ assert fastlsq.__version__ == "0.2.4"
24
24
 
25
25
 
26
26
  def test_imports():
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes