File: test_models.py

package info (click to toggle)
lmfit-py 1.3.3-4
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 2,332 kB
  • sloc: python: 13,071; makefile: 130; sh: 30
file content (154 lines) | stat: -rw-r--r-- 4,965 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
import numpy as np

import lmfit
from lmfit.lineshapes import gaussian
from lmfit.models import GaussianModel, SplineModel


def _isclose(name, expected_value, fit_value, atol, rtol):
    """isclose with error message"""
    assert np.isclose(expected_value, fit_value, atol=atol, rtol=rtol), \
           f"bad value for {name}: expected {expected_value}, got {fit_value}."


def check_fit(model, params, x, y, test_values, noise_scale=1.e-3, atol=0.1, rtol=0.05):
    """Checks that a model fits noisy data well

    Parameters
    -----------
    model:  model to use
    par:  parameters to use
    x:      x data
    y:      y data
    test_values: dict of 'true values'
    noise_scale: float, optional
           The standard deviation of noise that is added to the test data.
    atol: float, optional
           Absolute tolerance for considering fit parameters close to the
           parameters test data was generated with.
    rtol: float, optional
           Relative tolerance for considering fit parameters close to the
           parameters test data was generated with.

    Returns
    -------
      fit result

    Raises
    -------
       AssertionError
          Any fit parameter that is not close to the parameter used to
          generate the test data raises this error.
    """
    y += np.random.normal(scale=noise_scale, size=len(y))
    result = model.fit(y, params, x=x)
    fit_values = result.best_values
    for name, test_val in test_values.items():
        _isclose(name, test_val, fit_values[name], atol, rtol)
    return result


def testLinear():
    mod = lmfit.models.LinearModel()
    x = np.linspace(-1, 1, 201)
    y = 10*x + 2
    params = mod.make_params(intercept=1, slope=2)
    check_fit(mod, params, x, y, dict(intercept=2, slope=10))


def testQuadratic():
    mod = lmfit.models.QuadraticModel()
    x = np.linspace(-1, 1, 201)
    y = 0.3*x*x + 10*x + 2
    params = mod.make_params(a=0, b=5, c=1)
    check_fit(mod, params, x, y, dict(a=0.3, b=10, c=2))


def testSine_partialperiod():
    mod = lmfit.models.SineModel()
    x = np.linspace(-1, 1, 201)
    pars = dict(amplitude=1.5, frequency=0.9, shift=0.4)

    y = pars['amplitude']*np.sin(x*pars['frequency'] + pars['shift'])

    params = mod.make_params(amplitude=1, frequency=1, shift=-0.2)
    check_fit(mod, params, x, y, pars)


def testSineWithLine():
    mod = lmfit.models.SineModel() + lmfit.models.LinearModel()
    x = np.linspace(-5, 5, 501)
    pars = dict(amplitude=5.3, frequency=3.8, shift=0.1, intercept=8.2, slope=0.2)

    y = pars['amplitude']*np.sin(x*pars['frequency'] + pars['shift'])
    y += pars['intercept'] + x * pars['slope']

    params = mod.make_params(amplitude=10, frequency=4.5, shift=0.1,
                             intercept=10, slope=0)

    check_fit(mod, params, x, y, pars, noise_scale=0.02)


def testSineManyShifts():
    mod = lmfit.models.SineModel() + lmfit.models.LinearModel()
    x = np.linspace(-5, 5, 501)
    pars = dict(amplitude=5.3, frequency=3.8, intercept=8.2, slope=0.2)

    for shift in (0.1, 0.5, 1.0, 1.5):
        pars['shift'] = shift
        y = pars['amplitude']*np.sin(x*pars['frequency'] + pars['shift'])
        y += pars['intercept'] + x*pars['slope']

        params = mod.make_params(amplitude=10, frequency=4.5, shift=0.8,
                                 intercept=10, slope=0)

        check_fit(mod, params, x, y, pars, noise_scale=0.02)


def testSineModel_guess():
    mod = lmfit.models.SineModel()
    x = np.linspace(-10, 10, 201)
    pars = dict(amplitude=1.5, frequency=0.5, shift=0.4)

    y = pars['amplitude']*np.sin(x*pars['frequency'] + pars['shift'])

    params = mod.guess(y, x=x)
    assert params['amplitude'] > 0.5
    assert params['amplitude'] < 5.0
    assert params['frequency'] > 0.1
    assert params['frequency'] < 1.5
    assert params['shift'] > 0.0
    assert params['shift'] < 1.0


def testSineModel_make_params():
    mod = lmfit.models.SineModel()
    pars = mod.make_params(amplitude=1.5, frequency=0.5,
                           shift=dict(value=0.4, min=-10, max=10))

    assert pars['amplitude'].value > 1.4
    assert pars['amplitude'].value < 1.6
    assert pars['amplitude'].min == 0
    assert pars['amplitude'].max > 1e99
    assert pars['shift'].value > 0.35
    assert pars['shift'].value < 0.45
    assert pars['shift'].min < -9.5
    assert pars['shift'].max > 9.5


def testSplineModel():
    x = np.linspace(0, 25, 501)
    y = gaussian(x, amplitude=10, center=16.2, sigma=0.8) + 3 + 0.03*x + np.sin(x/4)

    model = GaussianModel(prefix='peak_')
    params = model.make_params(amplitude=8, center=16, sigma=1)

    knot_xvals = np.array([1, 3, 5, 7, 9, 11, 13, 19, 21, 23, 25])

    bkg = SplineModel(prefix='bkg_', xknots=knot_xvals)
    params.update(bkg.guess(y, x))

    model = model + bkg

    pars = dict(peak_amplitude=10, peak_center=16.2, peak_sigma=0.8)
    check_fit(model, params, x, y, pars, noise_scale=0.05)