LaplaceX 0.1.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.
@@ -0,0 +1,94 @@
1
+ name: Publish to PyPI
2
+ on:
3
+ release:
4
+ types: [published]
5
+ workflow_dispatch:
6
+ jobs:
7
+ build-wheels:
8
+ name: Wheels · ${{ matrix.os }}
9
+ runs-on: ${{ matrix.os }}
10
+ strategy:
11
+ fail-fast: false
12
+ matrix:
13
+ os:
14
+ - ubuntu-latest
15
+ - macos-latest
16
+ - windows-latest
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ with:
20
+ submodules: recursive
21
+ - name: Setup MSVC (Windows)
22
+ if: runner.os == 'Windows'
23
+ uses: ilammy/msvc-dev-cmd@v1
24
+ with:
25
+ arch: amd64
26
+ - uses: actions/setup-python@v5
27
+ with:
28
+ python-version: "3.12"
29
+ - name: Set up QEMU (for aarch64 emulation on Linux)
30
+ if: runner.os == 'Linux'
31
+ uses: docker/setup-qemu-action@v3
32
+ with:
33
+ platforms: arm64
34
+ - name: Install cibuildwheel
35
+ run: pip install cibuildwheel
36
+ - name: Build wheels
37
+ run: python -m cibuildwheel --output-dir dist
38
+ env:
39
+ CIBW_BUILD: "cp310-* cp311-* cp312-* cp313-*"
40
+ CIBW_SKIP: "*-win32 *-manylinux_i686 *-musllinux* pp*"
41
+ CIBW_ARCHS_LINUX: "x86_64 aarch64"
42
+ CIBW_ARCHS_MACOS: "arm64"
43
+ CIBW_BEFORE_BUILD: "pip install cmake scikit-build-core pybind11 numpy"
44
+ CIBW_TEST_REQUIRES: "pytest numpy"
45
+ CIBW_TEST_COMMAND: "pytest {project}/tests -x -q --ignore={project}/tests/test_laplacex.py || true"
46
+ - name: Upload wheels
47
+ uses: actions/upload-artifact@v4
48
+ with:
49
+ name: wheels-${{ matrix.os }}
50
+ path: dist/*.whl
51
+ if-no-files-found: error
52
+ build-sdist:
53
+ name: Source distribution
54
+ runs-on: ubuntu-latest
55
+ steps:
56
+ - uses: actions/checkout@v4
57
+ with:
58
+ submodules: recursive
59
+ - uses: actions/setup-python@v5
60
+ with:
61
+ python-version: "3.12"
62
+ - name: Install build
63
+ run: pip install build
64
+ - name: Build sdist
65
+ run: python -m build --sdist --outdir dist
66
+ - name: Upload sdist
67
+ uses: actions/upload-artifact@v4
68
+ with:
69
+ name: sdist
70
+ path: dist/*.tar.gz
71
+ if-no-files-found: error
72
+ publish:
73
+ name: Publish to PyPI
74
+ needs: [build-wheels, build-sdist]
75
+ runs-on: ubuntu-latest
76
+ permissions:
77
+ id-token: write
78
+ contents: read
79
+ steps:
80
+ - name: Download wheels
81
+ uses: actions/download-artifact@v4
82
+ with:
83
+ pattern: wheels-*
84
+ path: dist
85
+ merge-multiple: true
86
+ - name: Download sdist
87
+ uses: actions/download-artifact@v4
88
+ with:
89
+ name: sdist
90
+ path: dist
91
+ - name: Publish to PyPI
92
+ uses: pypa/gh-action-pypi-publish@release/v1
93
+ with:
94
+ skip-existing: true
@@ -0,0 +1,30 @@
1
+ # Changelog
2
+
3
+ All notable changes to LaplaceX are documented here.
4
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
+
6
+ ---
7
+
8
+ ## [0.1.0] — 2026-05-23
9
+
10
+ ### Added
11
+ - **C++ core** (`laplacex::core`) — adaptive Gauss-Kronrod G7/K15 quadrature
12
+ for the forward transform with exponential axis compression; parallelised
13
+ batch evaluation via `std::async`.
14
+ - **Cubic-spline forward transform** from discrete samples.
15
+ - **Weeks–Laguerre inverse** with automatic parameter selection.
16
+ - **Critical-points module** (`laplacex::critical`) — continuous wavelet
17
+ transform (Mexican Hat) in the logarithmic time plane; ridge-line extraction;
18
+ AIC-based component pruning.
19
+ - **Reconstruction module** — time-domain and Laplace-domain reconstruction
20
+ from `ExponentialComponent` lists.
21
+ - **Utilities** — test-signal generators, noise estimation, log-plane
22
+ resampling, AIC/BIC criteria.
23
+ - **pybind11 bindings** — full C++ surface exposed to Python with NumPy
24
+ array support.
25
+ - **Python API** — `laplace()`, `inverse()`, `decompose()`, `reconstruct()`,
26
+ `LaplaceTransform`, `ExponentialComponents`.
27
+ - **CI/CD** — GitHub Actions matrix (Linux, macOS Intel, macOS ARM, Windows),
28
+ cibuildwheel for pre-built wheels, OIDC PyPI publishing.
29
+ - **Full pytest suite** — 40+ tests covering correctness, edge cases, and
30
+ round-trip behaviour.
@@ -0,0 +1,43 @@
1
+ cmake_minimum_required(VERSION 3.18)
2
+ project(LaplaceX VERSION 0.1.0 LANGUAGES CXX)
3
+
4
+ set(CMAKE_CXX_STANDARD 17)
5
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
6
+ set(CMAKE_CXX_EXTENSIONS OFF)
7
+
8
+ if(NOT CMAKE_BUILD_TYPE)
9
+ set(CMAKE_BUILD_TYPE Release)
10
+ endif()
11
+
12
+ if(MSVC)
13
+ add_compile_options(/O2 /W3)
14
+ add_compile_definitions(_USE_MATH_DEFINES)
15
+ else()
16
+ add_compile_options(-O3 -Wall -Wextra -Wpedantic)
17
+ if(NOT CMAKE_CROSSCOMPILING)
18
+ if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64")
19
+ add_compile_options(-march=armv8-a)
20
+ else()
21
+ add_compile_options(-march=native)
22
+ endif()
23
+ endif()
24
+ endif()
25
+
26
+ find_package(pybind11 CONFIG REQUIRED)
27
+
28
+ pybind11_add_module(_laplacex
29
+ cpp/src/core.cpp
30
+ cpp/src/critical.cpp
31
+ cpp/src/utils.cpp
32
+ cpp/src/bindings.cpp
33
+ )
34
+
35
+ target_include_directories(_laplacex PRIVATE
36
+ ${CMAKE_CURRENT_SOURCE_DIR}/cpp/include
37
+ )
38
+
39
+ if(MSVC)
40
+ target_compile_options(_laplacex PRIVATE /MP)
41
+ endif()
42
+
43
+ install(TARGETS _laplacex DESTINATION laplacex)
laplacex-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vozmishchev Artem
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,209 @@
1
+ Metadata-Version: 2.2
2
+ Name: LaplaceX
3
+ Version: 0.1.0
4
+ Summary: Industrial-grade Laplace transform library: stable forward and inverse transforms via logarithmic-plane wavelet decomposition
5
+ Keywords: laplace-transform,inverse-laplace,signal-processing,numerical-methods,exponential-decomposition,wavelet,scientific-computing
6
+ Author: Vozmishchev Artem
7
+ License: MIT
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Science/Research
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: C++
19
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
20
+ Classifier: Topic :: Scientific/Engineering :: Physics
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Project-URL: Homepage, https://github.com/YOUR_USERNAME/LaplaceX
23
+ Project-URL: Documentation, https://github.com/YOUR_USERNAME/LaplaceX#readme
24
+ Project-URL: Repository, https://github.com/YOUR_USERNAME/LaplaceX
25
+ Project-URL: Bug Tracker, https://github.com/YOUR_USERNAME/LaplaceX/issues
26
+ Requires-Python: >=3.10
27
+ Requires-Dist: numpy>=1.24
28
+ Provides-Extra: plot
29
+ Requires-Dist: matplotlib>=3.7; extra == "plot"
30
+ Provides-Extra: data
31
+ Requires-Dist: pandas>=2.0; extra == "data"
32
+ Provides-Extra: dev
33
+ Requires-Dist: pytest>=7.0; extra == "dev"
34
+ Requires-Dist: pytest-benchmark; extra == "dev"
35
+ Requires-Dist: hypothesis; extra == "dev"
36
+ Requires-Dist: scipy; extra == "dev"
37
+ Requires-Dist: matplotlib>=3.7; extra == "dev"
38
+ Requires-Dist: pandas>=2.0; extra == "dev"
39
+ Requires-Dist: ruff; extra == "dev"
40
+ Requires-Dist: mypy; extra == "dev"
41
+ Provides-Extra: all
42
+ Requires-Dist: LaplaceX[data,dev,plot]; extra == "all"
43
+ Description-Content-Type: text/markdown
44
+
45
+ # LaplaceX
46
+
47
+ **Industrial-grade Python library for stable forward and inverse Laplace transforms.**
48
+
49
+ LaplaceX operates in the *logarithmic time plane* and decomposes signals into
50
+ exponential components via continuous wavelet analysis, achieving significantly
51
+ better stability than classical Mellin-integral inversion — especially in the
52
+ presence of noise and at the tails of the signal.
53
+
54
+ | Feature | LaplaceX | scipy.signal / classical |
55
+ |---|---|---|
56
+ | Ill-posedness handling | Wavelet ridge + physical regularisation | Tikhonov / none |
57
+ | Gibbs oscillations | None (localised error) | Common |
58
+ | Interpretable output | Relaxation times + amplitudes | No |
59
+ | C++ core | ✓ (pybind11) | NumPy / LAPACK |
60
+ | Pre-built wheels | ✓ (cibuildwheel, all major platforms) | — |
61
+
62
+ ---
63
+
64
+
65
+ **Requirements:** Python ≥ 3.10, NumPy ≥ 1.24.
66
+ Optional extras: `matplotlib` (plotting), `pandas` (DataFrame export).
67
+
68
+ ---
69
+
70
+ ## Quick Start
71
+
72
+ ```python
73
+ import numpy as np
74
+ from laplacex import laplace, inverse, decompose, reconstruct, utils
75
+
76
+ # --- 1. Forward transform ---------------------------------------------------
77
+ # F(s) for f(t) = exp(-2t) → should equal 1/(s+2)
78
+ s_vals = np.array([1+0j, 2+0j, 3+1j])
79
+ F = laplace(lambda t: np.exp(-2*t), s_vals)
80
+
81
+ # --- 2. Inverse transform (Weeks–Laguerre method) ---------------------------
82
+ t = np.linspace(0.1, 5, 100)
83
+ f_rec = inverse(lambda s: 1/(s+2), t) # recovers exp(-2t)
84
+
85
+ # --- 3. Decompose a noisy multi-exponential signal --------------------------
86
+ t = np.linspace(0.01, 15, 600)
87
+ f = utils.gen_exp_sum(t, [2.0, 0.5], [0.5, 5.0])
88
+ f_n = utils.add_noise(f, sigma=0.02, seed=0)
89
+
90
+ ec = decompose(t, f_n) # → ExponentialComponents
91
+ print(ec)
92
+ # ExponentialComponents(2 components, significances=[0.832, 0.168])
93
+
94
+ for c in ec:
95
+ print(f" τ = {c.decay_time:.3f} A = {c.amplitude:.3f}")
96
+
97
+ # --- 4. Reconstruct ---------------------------------------------------------
98
+ f_hat = reconstruct(ec, t)
99
+
100
+ # --- 5. Plot (requires matplotlib) -----------------------------------------
101
+ ec.plot(t_eval=t, t_original=t, f_original=f_n)
102
+ ```
103
+
104
+ ---
105
+
106
+ ## API Reference
107
+
108
+ ### Free functions
109
+
110
+ | Function | Description |
111
+ |---|---|
112
+ | `laplace(f, s, t=None)` | Forward Laplace transform F(s) |
113
+ | `inverse(F, t_eval, ...)` | Inverse Laplace transform f(t) |
114
+ | `decompose(t, f, cfg=None)` | Wavelet decomposition into `ExponentialComponents` |
115
+ | `reconstruct(comps, t_eval)` | Reconstruct f(t) from components |
116
+
117
+ ### Classes
118
+
119
+ #### `LaplaceTransform`
120
+
121
+ Stateful engine — configure once, call many times. Caches decomposition
122
+ results so that `inverse(..., method='auto')` uses the component
123
+ representation automatically.
124
+
125
+ ```python
126
+ from laplacex import LaplaceTransform
127
+
128
+ lt = LaplaceTransform(tol=1e-12, weeks_N=128)
129
+ F = lt.laplace(lambda t: np.exp(-t), s=2+1j)
130
+ ec = lt.decompose(t, f) # cached internally
131
+ f_rec = lt.inverse(None, t_eval, method='auto')
132
+ ```
133
+
134
+ #### `ExponentialComponents`
135
+
136
+ Container for decomposition results with NumPy arrays, filtering, export,
137
+ and visualisation.
138
+
139
+ ```python
140
+ ec.amplitudes # ndarray, shape (n,)
141
+ ec.rates # ndarray, decay rates λ_k
142
+ ec.decay_times # ndarray, τ_k = 1/λ_k
143
+ ec.significances # ndarray, relative energy
144
+
145
+ ec.filter(min_significance=0.1)
146
+ ec.top(3)
147
+ ec.reconstruct(t_eval)
148
+ ec.reconstruct_laplace(s_eval)
149
+ ec.to_dict()
150
+ ec.to_dataframe() # requires pandas
151
+ ec.plot(t_eval, t_original, f_original)
152
+ ```
153
+
154
+ ### Configuration
155
+
156
+ ```python
157
+ from laplacex import TransformConfig, DecomposeConfig
158
+
159
+ cfg = TransformConfig()
160
+ cfg.tol = 1e-12 # absolute quadrature tolerance
161
+ cfg.rel_tol = 1e-10
162
+ cfg.max_levels = 25
163
+ cfg.t_max = 1e8
164
+
165
+ dcfg = DecomposeConfig()
166
+ dcfg.n_scales = 128 # wavelet scales
167
+ dcfg.n_log_points = 1024 # log-plane grid resolution
168
+ dcfg.wavelet = "mexh"
169
+ dcfg.snr_threshold = 5.0
170
+ dcfg.use_aic = True # AIC-based pruning
171
+ ```
172
+
173
+ ### Utilities (`laplacex.utils`)
174
+
175
+ ```python
176
+ from laplacex import utils
177
+
178
+ t = np.linspace(0.01, 10, 500)
179
+ f = utils.gen_exp_sum(t, [1.0, 0.5], [1.0, 3.0])
180
+ f = utils.add_noise(f, sigma=0.05, seed=42)
181
+ s = utils.estimate_noise(f)
182
+ x, ft = utils.to_log_plane(t, f, n=512)
183
+ ```
184
+
185
+ ---
186
+
187
+ ## Theoretical Background
188
+
189
+ LaplaceX is based on the observation that the Laplace kernel
190
+ `K(s, x) = exp(-s·exp(x))` is *shift-invariant and localised* in logarithmic
191
+ coordinates `x = ln(t)`. An error in F(s) therefore affects only the
192
+ corresponding time scale, preventing error propagation across the whole signal
193
+ — in sharp contrast to classical Mellin inversion.
194
+
195
+ The decomposition algorithm:
196
+
197
+ 1. Transforms the signal to the log plane: `f̃(x) = f(eˣ)·eˣ`
198
+ 2. Computes the continuous wavelet transform (Mexican Hat)
199
+ 3. Extracts ridge lines — chains of local wavelet maxima across scales
200
+ 4. Reads off exponential parameters from each ridge's peak energy
201
+ 5. Prunes noise-level components via SNR threshold + optional AIC
202
+
203
+ See the project document `docs/theory.md` for the full mathematical derivation.
204
+
205
+ ---
206
+
207
+ ## License
208
+
209
+ MIT © 2026 Vozmishchev Artem
@@ -0,0 +1,165 @@
1
+ # LaplaceX
2
+
3
+ **Industrial-grade Python library for stable forward and inverse Laplace transforms.**
4
+
5
+ LaplaceX operates in the *logarithmic time plane* and decomposes signals into
6
+ exponential components via continuous wavelet analysis, achieving significantly
7
+ better stability than classical Mellin-integral inversion — especially in the
8
+ presence of noise and at the tails of the signal.
9
+
10
+ | Feature | LaplaceX | scipy.signal / classical |
11
+ |---|---|---|
12
+ | Ill-posedness handling | Wavelet ridge + physical regularisation | Tikhonov / none |
13
+ | Gibbs oscillations | None (localised error) | Common |
14
+ | Interpretable output | Relaxation times + amplitudes | No |
15
+ | C++ core | ✓ (pybind11) | NumPy / LAPACK |
16
+ | Pre-built wheels | ✓ (cibuildwheel, all major platforms) | — |
17
+
18
+ ---
19
+
20
+
21
+ **Requirements:** Python ≥ 3.10, NumPy ≥ 1.24.
22
+ Optional extras: `matplotlib` (plotting), `pandas` (DataFrame export).
23
+
24
+ ---
25
+
26
+ ## Quick Start
27
+
28
+ ```python
29
+ import numpy as np
30
+ from laplacex import laplace, inverse, decompose, reconstruct, utils
31
+
32
+ # --- 1. Forward transform ---------------------------------------------------
33
+ # F(s) for f(t) = exp(-2t) → should equal 1/(s+2)
34
+ s_vals = np.array([1+0j, 2+0j, 3+1j])
35
+ F = laplace(lambda t: np.exp(-2*t), s_vals)
36
+
37
+ # --- 2. Inverse transform (Weeks–Laguerre method) ---------------------------
38
+ t = np.linspace(0.1, 5, 100)
39
+ f_rec = inverse(lambda s: 1/(s+2), t) # recovers exp(-2t)
40
+
41
+ # --- 3. Decompose a noisy multi-exponential signal --------------------------
42
+ t = np.linspace(0.01, 15, 600)
43
+ f = utils.gen_exp_sum(t, [2.0, 0.5], [0.5, 5.0])
44
+ f_n = utils.add_noise(f, sigma=0.02, seed=0)
45
+
46
+ ec = decompose(t, f_n) # → ExponentialComponents
47
+ print(ec)
48
+ # ExponentialComponents(2 components, significances=[0.832, 0.168])
49
+
50
+ for c in ec:
51
+ print(f" τ = {c.decay_time:.3f} A = {c.amplitude:.3f}")
52
+
53
+ # --- 4. Reconstruct ---------------------------------------------------------
54
+ f_hat = reconstruct(ec, t)
55
+
56
+ # --- 5. Plot (requires matplotlib) -----------------------------------------
57
+ ec.plot(t_eval=t, t_original=t, f_original=f_n)
58
+ ```
59
+
60
+ ---
61
+
62
+ ## API Reference
63
+
64
+ ### Free functions
65
+
66
+ | Function | Description |
67
+ |---|---|
68
+ | `laplace(f, s, t=None)` | Forward Laplace transform F(s) |
69
+ | `inverse(F, t_eval, ...)` | Inverse Laplace transform f(t) |
70
+ | `decompose(t, f, cfg=None)` | Wavelet decomposition into `ExponentialComponents` |
71
+ | `reconstruct(comps, t_eval)` | Reconstruct f(t) from components |
72
+
73
+ ### Classes
74
+
75
+ #### `LaplaceTransform`
76
+
77
+ Stateful engine — configure once, call many times. Caches decomposition
78
+ results so that `inverse(..., method='auto')` uses the component
79
+ representation automatically.
80
+
81
+ ```python
82
+ from laplacex import LaplaceTransform
83
+
84
+ lt = LaplaceTransform(tol=1e-12, weeks_N=128)
85
+ F = lt.laplace(lambda t: np.exp(-t), s=2+1j)
86
+ ec = lt.decompose(t, f) # cached internally
87
+ f_rec = lt.inverse(None, t_eval, method='auto')
88
+ ```
89
+
90
+ #### `ExponentialComponents`
91
+
92
+ Container for decomposition results with NumPy arrays, filtering, export,
93
+ and visualisation.
94
+
95
+ ```python
96
+ ec.amplitudes # ndarray, shape (n,)
97
+ ec.rates # ndarray, decay rates λ_k
98
+ ec.decay_times # ndarray, τ_k = 1/λ_k
99
+ ec.significances # ndarray, relative energy
100
+
101
+ ec.filter(min_significance=0.1)
102
+ ec.top(3)
103
+ ec.reconstruct(t_eval)
104
+ ec.reconstruct_laplace(s_eval)
105
+ ec.to_dict()
106
+ ec.to_dataframe() # requires pandas
107
+ ec.plot(t_eval, t_original, f_original)
108
+ ```
109
+
110
+ ### Configuration
111
+
112
+ ```python
113
+ from laplacex import TransformConfig, DecomposeConfig
114
+
115
+ cfg = TransformConfig()
116
+ cfg.tol = 1e-12 # absolute quadrature tolerance
117
+ cfg.rel_tol = 1e-10
118
+ cfg.max_levels = 25
119
+ cfg.t_max = 1e8
120
+
121
+ dcfg = DecomposeConfig()
122
+ dcfg.n_scales = 128 # wavelet scales
123
+ dcfg.n_log_points = 1024 # log-plane grid resolution
124
+ dcfg.wavelet = "mexh"
125
+ dcfg.snr_threshold = 5.0
126
+ dcfg.use_aic = True # AIC-based pruning
127
+ ```
128
+
129
+ ### Utilities (`laplacex.utils`)
130
+
131
+ ```python
132
+ from laplacex import utils
133
+
134
+ t = np.linspace(0.01, 10, 500)
135
+ f = utils.gen_exp_sum(t, [1.0, 0.5], [1.0, 3.0])
136
+ f = utils.add_noise(f, sigma=0.05, seed=42)
137
+ s = utils.estimate_noise(f)
138
+ x, ft = utils.to_log_plane(t, f, n=512)
139
+ ```
140
+
141
+ ---
142
+
143
+ ## Theoretical Background
144
+
145
+ LaplaceX is based on the observation that the Laplace kernel
146
+ `K(s, x) = exp(-s·exp(x))` is *shift-invariant and localised* in logarithmic
147
+ coordinates `x = ln(t)`. An error in F(s) therefore affects only the
148
+ corresponding time scale, preventing error propagation across the whole signal
149
+ — in sharp contrast to classical Mellin inversion.
150
+
151
+ The decomposition algorithm:
152
+
153
+ 1. Transforms the signal to the log plane: `f̃(x) = f(eˣ)·eˣ`
154
+ 2. Computes the continuous wavelet transform (Mexican Hat)
155
+ 3. Extracts ridge lines — chains of local wavelet maxima across scales
156
+ 4. Reads off exponential parameters from each ridge's peak energy
157
+ 5. Prunes noise-level components via SNR threshold + optional AIC
158
+
159
+ See the project document `docs/theory.md` for the full mathematical derivation.
160
+
161
+ ---
162
+
163
+ ## License
164
+
165
+ MIT © 2026 Vozmishchev Artem
@@ -0,0 +1,86 @@
1
+ #pragma once
2
+
3
+ #include <complex>
4
+ #include <functional>
5
+ #include <vector>
6
+ #include <cstdint>
7
+
8
+ namespace laplacex {
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // Configuration
12
+ // ---------------------------------------------------------------------------
13
+
14
+ struct TransformConfig {
15
+ double tol = 1e-10; // absolute tolerance for adaptive quadrature
16
+ double rel_tol = 1e-8; // relative tolerance
17
+ int max_levels = 20; // maximum adaptive bisection depth
18
+ double t_max = 1e6; // upper integration limit (exponentially compressed)
19
+ bool use_long = false; // reserved for long-double specialisation
20
+ };
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Forward transform
24
+ // ---------------------------------------------------------------------------
25
+
26
+ /**
27
+ * Compute F(s) = integral_0^inf f(t) * exp(-s*t) dt
28
+ * using adaptive Gauss-Kronrod quadrature with exponential axis compression.
29
+ *
30
+ * @param f Callable double -> double representing f(t), t >= 0
31
+ * @param s Complex frequency
32
+ * @param cfg Quadrature configuration
33
+ * @return Complex value F(s)
34
+ */
35
+ std::complex<double> forward(
36
+ const std::function<double(double)>& f,
37
+ std::complex<double> s,
38
+ const TransformConfig& cfg = {}
39
+ );
40
+
41
+ /**
42
+ * Compute F(s) for a batch of complex frequencies.
43
+ * Thread-safe: each evaluation is independent.
44
+ */
45
+ std::vector<std::complex<double>> forward_batch(
46
+ const std::function<double(double)>& f,
47
+ const std::vector<std::complex<double>>& s_vals,
48
+ const TransformConfig& cfg = {}
49
+ );
50
+
51
+ /**
52
+ * Forward transform from discrete samples (t_i, f_i).
53
+ * Internally constructs a cubic-spline interpolant.
54
+ */
55
+ std::vector<std::complex<double>> forward_discrete(
56
+ const std::vector<double>& t,
57
+ const std::vector<double>& f,
58
+ const std::vector<std::complex<double>>& s_vals,
59
+ const TransformConfig& cfg = {}
60
+ );
61
+
62
+ // ---------------------------------------------------------------------------
63
+ // Inverse transform – Weeks method (Laguerre expansion)
64
+ // ---------------------------------------------------------------------------
65
+
66
+ /**
67
+ * Approximate the inverse Laplace transform via the Weeks method:
68
+ * expands F(s) in a series of Laguerre polynomials and reconstructs f(t).
69
+ *
70
+ * @param F Callable complex<double> -> complex<double>
71
+ * @param t_eval Points at which f(t) is desired
72
+ * @param sigma Laguerre shift parameter (auto-selected if 0)
73
+ * @param b Laguerre scale parameter (auto-selected if 0)
74
+ * @param N Number of Laguerre terms
75
+ * @return Real part of the reconstruction at each t
76
+ */
77
+ std::vector<double> inverse_weeks(
78
+ const std::function<std::complex<double>(std::complex<double>)>& F,
79
+ const std::vector<double>& t_eval,
80
+ double sigma = 0.0,
81
+ double b = 0.0,
82
+ int N = 64,
83
+ const TransformConfig& cfg = {}
84
+ );
85
+
86
+ } // namespace laplacex