Skip to content

Stretched exponential - #245

Open
henrikjacobsenfys wants to merge 2 commits into
masterfrom
stretched-exponential
Open

henrikjacobsenfys wants to merge 2 commits into
masterfrom
stretched-exponential

Conversation

@henrikjacobsenfys

Copy link
Copy Markdown
Member

Implementing the Fourier transform of a stretched exponential. It's not trivial to make it fast and correct at the same time. Claude did that for me. I have updated the text and read through the whole thing.

@henrikjacobsenfys henrikjacobsenfys added [scope] enhancement Adds/improves features (major.MINOR.patch) [priority] medium Normal/default priority labels Sep 9, 2026
@codecov

codecov Bot commented Sep 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.51%. Comparing base (f941728) to head (842376f).
⚠️ Report is 1 commits behind head on master.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #245      +/-   ##
==========================================
+ Coverage   98.47%   98.51%   +0.03%     
==========================================
  Files          57       58       +1     
  Lines        5512     5646     +134     
  Branches      952      961       +9     
==========================================
+ Hits         5428     5562     +134     
  Misses         42       42              
  Partials       42       42              
Flag Coverage Δ
unittests 98.51% <100.00%> (+0.03%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/easydynamics/__init__.py 100.00% <100.00%> (ø)
src/easydynamics/sample_model/__init__.py 100.00% <100.00%> (ø)
...c/easydynamics/sample_model/components/__init__.py 100.00% <100.00%> (ø)
...s/sample_model/components/stretched_exponential.py 100.00% <100.00%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@rozyczko rozyczko left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to merge

r"""
The stretched exponential (Kohlrausch-Williams-Watts) relaxation, Fourier transformed to energy.

The model is defined in time, as $I(t) = A e^{-(|t| / \tau)^\beta}$. To evalaute the Fourier

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

evalaute -> evaluate

``scipy.stats.levy_stable`` is mathematically the same function, but it is 70-100x slower and not
accurate for $\alpha$ close to 1.

Instad, the integral is evaluated in the complex plane described in :func:`_kww_shape`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instad -> Instead


@property
def width(self) -> DescriptorNumber:
r"""

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

width returns a DescriptorNumber, whereas every other component's width is a Parameter.
Anything that does comp.width.fixed, comp.width.make_dependent_on(...) or puts it in a FitBinding will fail.

@rozyczko

Copy link
Copy Markdown
Member

I ran Fable on this PR and it found an issue I can't comment on, since I don't know science well enough.

=====================================================================

width overstates the true half width by orders of magnitude at small β, so the numerical convolution's grid-resolution warning never fires and coarse grids give wrong results silently (medium)

width is exposed as $\Gamma = \hbar/\tau$
(stretched_exponential.py:493-515) so that the shared
width-versus-grid checks in
NumericalConvolutionBase._check_width_thresholds can see how wide the
component is. But $\Gamma$ is only the HWHM at $\beta = 1$. Below that,
the peak sharpens dramatically — the height grows as
$\Gamma(1 + 1/\beta)$ while the area stays fixed — and the actual HWHM
collapses:

$\beta$ HWHM / $\Gamma$ peak height relative to a Lorentzian of the same $\Gamma$
1.0 1.00 1.0
0.7 0.56 1.3
0.5 0.22 2.0
0.3 1.5e-2 9.3
0.2 2.7e-4 120
0.1 < 1e-8 3.6e6

So a component with $\tau = 5$ ps, $\beta = 0.2$ reports
width = 0.13 meV (comfortably above a 0.1 meV grid step, no warning),
while its real HWHM is $3.5 \times 10^{-5}$ meV. The numerical
convolution then samples a sub-grid spike and returns garbage. Measured
with a 0.05 meV Gaussian resolution on $[-2, 2]$ meV, default
upsample_factor=5:

$\beta$ grid points convolved peak area on $[-2,2]$ warnings
0.3 81 3.71 0.818 none
0.3 401 2.71 0.693 none
0.3 40001 (converged) 2.64 0.684 none
0.2 81 28.7 3.85 none
0.2 401 7.40 1.18 none
0.2 4001 3.03 0.630 none
0.2 40001 (converged) 2.75 0.595 none

At $\beta = 0.2$ on an 81-point grid the area is wrong by a factor of
six and nothing tells the user. $\beta = 0.6$ is fine on every grid, so
this only bites in the small-$\beta$ regime — but that is exactly the
regime where people reach for a KWW instead of a Lorentzian.

Note this is not a bug in _kww_shape; the direct evaluate is exact.
It is a mismatch between what width promises to the convolution
machinery and what the profile actually looks like.

Suggested fix (pick one):

  1. Make width report an actual half width. There is no closed form,
    but a bisection on _kww_shape for the $w$ where
    $G_\beta(w) = \tfrac12,\Gamma(1 + 1/\beta)$ costs ~40 single-point
    quadratures (microseconds) and width is not on the evaluation hot
    path. Then the existing small-width warning fires exactly when it
    should. Keep the docstring note that this is derived and read-only.
  2. Failing that, document loudly in the class docstring that $\Gamma$ is
    not the HWHM for $\beta &lt; 1$, that the numerical convolution needs
    upsample_factor raised for small $\beta$, and add a regression test
    that the convolution converges (compare 401 vs 4001 points at
    $\beta = 0.3$).

Either way, add a test that a narrow small-$\beta$ component on a coarse
grid produces the "small compared to the spacing" warning — today it
does not.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

[priority] medium Normal/default priority [scope] enhancement Adds/improves features (major.MINOR.patch)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement a fourier transformed stretched exponential modelcomponent

2 participants