Skip to content

Commit 7df984d

Browse files
committed
Merge branch 'main' into depwarn
2 parents df6e193 + a022997 commit 7df984d

File tree

7 files changed

+197
-34
lines changed

7 files changed

+197
-34
lines changed

fooof/core/funcs.py

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,7 @@ def gaussian_function(xs, *params):
3232

3333
ys = np.zeros_like(xs)
3434

35-
for ii in range(0, len(params), 3):
36-
37-
ctr, hgt, wid = params[ii:ii+3]
35+
for ctr, hgt, wid in zip(*[iter(params)] * 3):
3836

3937
ys = ys + hgt * np.exp(-(xs-ctr)**2 / (2*wid**2))
4038

@@ -60,11 +58,8 @@ def expo_function(xs, *params):
6058
Output values for exponential function.
6159
"""
6260

63-
ys = np.zeros_like(xs)
64-
6561
offset, knee, exp = params
66-
67-
ys = ys + offset - np.log10(knee + xs**exp)
62+
ys = offset - np.log10(knee + xs**exp)
6863

6964
return ys
7065

@@ -88,11 +83,8 @@ def expo_nk_function(xs, *params):
8883
Output values for exponential function, without a knee.
8984
"""
9085

91-
ys = np.zeros_like(xs)
92-
9386
offset, exp = params
94-
95-
ys = ys + offset - np.log10(xs**exp)
87+
ys = offset - np.log10(xs**exp)
9688

9789
return ys
9890

@@ -113,11 +105,8 @@ def linear_function(xs, *params):
113105
Output values for linear function.
114106
"""
115107

116-
ys = np.zeros_like(xs)
117-
118108
offset, slope = params
119-
120-
ys = ys + offset + (xs*slope)
109+
ys = offset + (xs*slope)
121110

122111
return ys
123112

@@ -138,11 +127,8 @@ def quadratic_function(xs, *params):
138127
Output values for quadratic function.
139128
"""
140129

141-
ys = np.zeros_like(xs)
142-
143130
offset, slope, curve = params
144-
145-
ys = ys + offset + (xs*slope) + ((xs**2)*curve)
131+
ys = offset + (xs*slope) + ((xs**2)*curve)
146132

147133
return ys
148134

fooof/core/jacobians.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
""""Functions for computing Jacobian matrices to be used during fitting.
2+
3+
Notes
4+
-----
5+
These functions line up with those in `funcs`.
6+
The parameters in these functions are labeled {a, b, c, ...}, but follow the order in `funcs`.
7+
These functions are designed to be passed into `curve_fit` to provide a computed Jacobian.
8+
"""
9+
10+
import numpy as np
11+
12+
###################################################################################################
13+
###################################################################################################
14+
15+
## Periodic Jacobian functions
16+
17+
def jacobian_gauss(xs, *params):
18+
"""Create the Jacobian matrix for the Gaussian function.
19+
20+
Parameters
21+
----------
22+
xs : 1d array
23+
Input x-axis values.
24+
*params : float
25+
Parameters for the function.
26+
27+
Returns
28+
-------
29+
jacobian : 2d array
30+
Jacobian matrix, with shape [len(xs), n_params].
31+
"""
32+
33+
jacobian = np.zeros((len(xs), len(params)))
34+
35+
for i, (a, b, c) in enumerate(zip(*[iter(params)] * 3)):
36+
37+
ax = -a + xs
38+
ax2 = ax**2
39+
40+
c2 = c**2
41+
c3 = c**3
42+
43+
exp = np.exp(-ax2 / (2 * c2))
44+
exp_b = exp * b
45+
46+
ii = i * 3
47+
jacobian[:, ii] = (exp_b * ax) / c2
48+
jacobian[:, ii+1] = exp
49+
jacobian[:, ii+2] = (exp_b * ax2) / c3
50+
51+
return jacobian
52+
53+
54+
## Aperiodic Jacobian functions
55+
56+
def jacobian_expo(xs, *params):
57+
"""Create the Jacobian matrix for the exponential function.
58+
59+
Parameters
60+
----------
61+
xs : 1d array
62+
Input x-axis values.
63+
*params : float
64+
Parameters for the function.
65+
66+
Returns
67+
-------
68+
jacobian : 2d array
69+
Jacobian matrix, with shape [len(xs), n_params].
70+
"""
71+
72+
a, b, c = params
73+
74+
xs_c = xs**c
75+
b_xs_c = xs_c + b
76+
77+
jacobian = np.ones((len(xs), len(params)))
78+
jacobian[:, 1] = -1 / b_xs_c
79+
jacobian[:, 2] = -(xs_c * np.log10(xs)) / b_xs_c
80+
81+
return jacobian
82+
83+
84+
def jacobian_expo_nk(xs, *params):
85+
"""Create the Jacobian matrix for the exponential no-knee function.
86+
87+
Parameters
88+
----------
89+
xs : 1d array
90+
Input x-axis values.
91+
*params : float
92+
Parameters for the function.
93+
94+
Returns
95+
-------
96+
jacobian : 2d array
97+
Jacobian matrix, with shape [len(xs), n_params].
98+
"""
99+
100+
jacobian = np.ones((len(xs), len(params)))
101+
jacobian[:, 1] = -np.log10(xs)
102+
103+
return jacobian

fooof/objs/fit.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
from fooof.core.modutils import copy_doc_func_to_method
7272
from fooof.core.utils import group_three, check_array_dim
7373
from fooof.core.funcs import gaussian_function, get_ap_func, infer_ap_func
74+
from fooof.core.jacobians import jacobian_gauss
7475
from fooof.core.errors import (FitError, NoModelError, DataError,
7576
NoDataError, InconsistentDataError)
7677
from fooof.core.strings import (gen_settings_str, gen_results_fm_str,
@@ -192,12 +193,17 @@ def __init__(self, peak_width_limits=(0.5, 12.0), max_n_peaks=np.inf, min_peak_h
192193
self._gauss_overlap_thresh = 0.75
193194
# Parameter bounds for center frequency when fitting gaussians, in terms of +/- std dev
194195
self._cf_bound = 1.5
195-
# The maximum number of calls to the curve fitting function
196-
self._maxfev = 5000
197196
# The error metric to calculate, post model fitting. See `_calc_error` for options
198197
# Note: this is for checking error post fitting, not an objective function for fitting
199198
self._error_metric = 'MAE'
200199

200+
## PRIVATE CURVE_FIT SETTINGS
201+
# The maximum number of calls to the curve fitting function
202+
self._maxfev = 5000
203+
# The tolerance setting for curve fitting (see scipy.curve_fit - ftol / xtol / gtol)
204+
# Here reduce tolerance to speed fitting. Set value to 1e-8 to match curve_fit default
205+
self._tol = 0.00001
206+
201207
## RUN MODES
202208
# Set default debug mode - controls if an error is raised if model fitting is unsuccessful
203209
self._debug = False
@@ -946,7 +952,9 @@ def _simple_ap_fit(self, freqs, power_spectrum):
946952
warnings.simplefilter("ignore")
947953
aperiodic_params, _ = curve_fit(get_ap_func(self.aperiodic_mode),
948954
freqs, power_spectrum, p0=guess,
949-
maxfev=self._maxfev, bounds=ap_bounds)
955+
maxfev=self._maxfev, bounds=ap_bounds,
956+
ftol=self._tol, xtol=self._tol, gtol=self._tol,
957+
check_finite=False)
950958
except RuntimeError as excp:
951959
error_msg = ("Model fitting failed due to not finding parameters in "
952960
"the simple aperiodic component fit.")
@@ -1003,7 +1011,9 @@ def _robust_ap_fit(self, freqs, power_spectrum):
10031011
warnings.simplefilter("ignore")
10041012
aperiodic_params, _ = curve_fit(get_ap_func(self.aperiodic_mode),
10051013
freqs_ignore, spectrum_ignore, p0=popt,
1006-
maxfev=self._maxfev, bounds=ap_bounds)
1014+
maxfev=self._maxfev, bounds=ap_bounds,
1015+
ftol=self._tol, xtol=self._tol, gtol=self._tol,
1016+
check_finite=False)
10071017
except RuntimeError as excp:
10081018
error_msg = ("Model fitting failed due to not finding "
10091019
"parameters in the robust aperiodic fit.")
@@ -1149,7 +1159,9 @@ def _fit_peak_guess(self, guess):
11491159
# Fit the peaks
11501160
try:
11511161
gaussian_params, _ = curve_fit(gaussian_function, self.freqs, self._spectrum_flat,
1152-
p0=guess, maxfev=self._maxfev, bounds=gaus_param_bounds)
1162+
p0=guess, maxfev=self._maxfev, bounds=gaus_param_bounds,
1163+
ftol=self._tol, xtol=self._tol, gtol=self._tol,
1164+
check_finite=False, jac=jacobian_gauss)
11531165
except RuntimeError as excp:
11541166
error_msg = ("Model fitting failed due to not finding "
11551167
"parameters in the peak component fit.")

fooof/tests/core/test_jacobians.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""Tests for fooof.core.jacobians."""
2+
3+
from fooof.core.jacobians import *
4+
5+
###################################################################################################
6+
###################################################################################################
7+
8+
def test_jacobian_gauss():
9+
10+
xs = np.arange(1, 100)
11+
ctr, hgt, wid = 50, 5, 10
12+
13+
jacobian = jacobian_gauss(xs, ctr, hgt, wid)
14+
assert isinstance(jacobian, np.ndarray)
15+
assert jacobian.shape == (len(xs), 3)
16+
17+
def test_jacobian_expo():
18+
19+
xs = np.arange(1, 100)
20+
off, knee, exp = 10, 5, 2
21+
22+
jacobian = jacobian_expo(xs, off, knee, exp)
23+
assert isinstance(jacobian, np.ndarray)
24+
assert jacobian.shape == (len(xs), 3)
25+
26+
def test_jacobian_expo_nk():
27+
28+
xs = np.arange(1, 100)
29+
off, exp = 10, 2
30+
31+
jacobian = jacobian_expo_nk(xs, off, exp)
32+
assert isinstance(jacobian, np.ndarray)
33+
assert jacobian.shape == (len(xs), 2)

fooof/tests/objs/test_fit.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ def test_fooof_fit_failure():
391391

392392
## Induce a runtime error, and check it runs through
393393
tfm = FOOOF(verbose=False)
394-
tfm._maxfev = 5
394+
tfm._maxfev = 2
395395

396396
tfm.fit(*gen_power_spectrum([3, 50], [50, 2], [10, 0.5, 2, 20, 0.3, 4]))
397397

@@ -417,7 +417,7 @@ def test_fooof_debug():
417417
"""Test FOOOF in debug mode, including with fit failures."""
418418

419419
tfm = FOOOF(verbose=False)
420-
tfm._maxfev = 5
420+
tfm._maxfev = 2
421421

422422
tfm.set_debug_mode(True)
423423
assert tfm._debug is True

fooof/tests/utils/test_params.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def test_compute_knee_frequency():
1313

1414
def test_compute_time_constant():
1515

16-
assert compute_time_constant(100)
16+
assert compute_time_constant(10)
1717

1818
def test_compute_fwhm():
1919

fooof/utils/params.py

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,26 +19,55 @@ def compute_knee_frequency(knee, exponent):
1919
-------
2020
float
2121
Frequency value, in Hz, of the knee occurs.
22+
23+
Notes
24+
-----
25+
The knee frequency is an estimate of the frequency in spectrum at which the spectrum
26+
moves from the plateau region to the exponential decay.
27+
28+
This approach for estimating the knee frequency comes from [1]_ (see [2]_ for code).
29+
30+
Note that this provides an estimate of the knee frequency, but is not, in the general case,
31+
a precisely defined value. In particular, this conversion is based on the case of a Lorentzian
32+
with exponent = 2, and for other exponent values provides a non-exact approximation.
33+
34+
References
35+
----------
36+
.. [1] Gao, R., van den Brink, R. L., Pfeffer, T., & Voytek, B. (2020). Neuronal timescales
37+
are functionally dynamic and shaped by cortical microarchitecture. Elife, 9, e61277.
38+
https://doi.org/10.7554/eLife.61277
39+
.. [2] https://github.com/rdgao/field-echos/blob/master/echo_utils.py#L64
2240
"""
2341

24-
return knee ** (1./exponent)
42+
return knee ** (1. / exponent)
2543

2644

27-
def compute_time_constant(knee):
28-
"""Compute the characteristic time constant based on the knee value.
45+
def compute_time_constant(knee_freq):
46+
"""Compute the characteristic time constant from the estimated knee frequency.
2947
3048
Parameters
3149
----------
32-
knee : float
33-
Knee parameter value.
50+
knee_freq : float
51+
Estimated knee frequency.
3452
3553
Returns
3654
-------
3755
float
38-
Calculated time constant value, tau, given the knee parameter.
56+
Calculated time constant value, tau, given the knee frequency.
57+
58+
Notes
59+
-----
60+
This approach for estimating the time constant comes from [1]_ (see [2]_ for code).
61+
62+
References
63+
----------
64+
.. [1] Gao, R., van den Brink, R. L., Pfeffer, T., & Voytek, B. (2020). Neuronal timescales
65+
are functionally dynamic and shaped by cortical microarchitecture. Elife, 9, e61277.
66+
https://doi.org/10.7554/eLife.61277
67+
.. [2] https://github.com/rdgao/field-echos/blob/master/echo_utils.py#L65
3968
"""
4069

41-
return 1. / (2*np.pi*knee)
70+
return 1. / (2 * np.pi * knee_freq)
4271

4372

4473
def compute_fwhm(std):

0 commit comments

Comments
 (0)