diff --git a/specparam/modes/definitions.py b/specparam/modes/definitions.py index 4f7c540f..b7ecd20c 100644 --- a/specparam/modes/definitions.py +++ b/specparam/modes/definitions.py @@ -6,7 +6,8 @@ from specparam.modes.mode import Mode from specparam.modes.params import ParamDefinition from specparam.modes.funcs import (expo_function, expo_nk_function, double_expo_function, - gaussian_function, skewed_gaussian_function, cauchy_function) + gaussian_function, skewed_gaussian_function, + cauchy_function, triangle_function) from specparam.modes.jacobians import jacobian_gauss from specparam.utils.checks import check_selection @@ -149,12 +150,33 @@ powers_space='log10', ) +## PE - Triangle Mode + +params_triangle = ParamDefinition(OrderedDict({ + 'cf' : 'Center frequency of the peak.', + 'pw' : 'Power of the peak, over and above the aperiodic component.', + 'bw' : 'Bandwidth of the peak.', +})) + +pe_triangle = Mode( + name='triangle', + component='periodic', + description='Triangle peak fit function.', + func=triangle_function, + jacobian=None, + params=params_triangle, + ndim=2, + freq_space='linear', + powers_space='log10', +) + # Collect available periodic modes PE_MODES = { 'gaussian' : pe_gaussian, 'skewed_gaussian' : pe_skewed_gaussian, 'cauchy' : pe_cauchy, + 'triangle' : pe_triangle, } ################################################################################################### diff --git a/specparam/modes/funcs.py b/specparam/modes/funcs.py index 625a3b4a..f4ba97fe 100644 --- a/specparam/modes/funcs.py +++ b/specparam/modes/funcs.py @@ -53,9 +53,7 @@ def skewed_gaussian_function(xs, *params): ys = np.zeros_like(xs) - for ii in range(0, len(params), 4): - - ctr, hgt, wid, skew = params[ii:ii+4] + for ctr, hgt, wid, skew in zip(*[iter(params)] * 4): ts = (xs - ctr) / wid temp = 2 / wid * (1 / np.sqrt(2 * np.pi) * np.exp(-ts**2 / 2)) * \ @@ -90,6 +88,35 @@ def cauchy_function(xs, *params): return ys +def triangle_function(xs, *params): + """Triangle fitting function. + + Parameters + ---------- + xs : 1d array + Input x-axis values. + *params : float + Parameters that define a triangle function. + + Returns + ------- + ys : 1d array + Output values for triangle function. + """ + + ys = np.zeros_like(xs) + fres = xs[1] - xs[0] + + for ctr, hgt, wid in zip(*[iter(params)] * 3): + + n_samples = int(np.ceil(2 * wid / fres)) + n_samples += 1 if n_samples % 2 == 0 else 0 + temp = np.arccos(np.cos(np.linspace(0, 2 * np.pi, n_samples))) + ys[np.abs(xs - ctr) <= (n_samples / 2) * fres] += hgt * normalize(temp) + + return ys + + ## APERIODIC FUNCTIONS def expo_function(xs, *params): diff --git a/specparam/tests/modes/test_funcs.py b/specparam/tests/modes/test_funcs.py index 069136ba..a831bfd6 100644 --- a/specparam/tests/modes/test_funcs.py +++ b/specparam/tests/modes/test_funcs.py @@ -13,10 +13,8 @@ def test_gaussian_function(): ctr, hgt, wid = 50, 5, 10 - - xs = np.arange(1, 100) + xs = np.arange(1, 100, 1.) ys = gaussian_function(xs, ctr, hgt, wid) - assert np.all(ys) # Check distribution matches generated gaussian from scipy @@ -28,7 +26,7 @@ def test_skewed_gaussian_function(): # Check that with no skew, approximate gaussian ctr, hgt, wid, skew = 50, 5, 10, 1 - xs = np.arange(1, 100) + xs = np.arange(1, 100, 1.) ys_gaus = gaussian_function(xs, ctr, hgt, wid) ys_skew = skewed_gaussian_function(xs, ctr, hgt, wid, skew) np.allclose(ys_gaus, ys_skew, atol=0.001) @@ -46,19 +44,26 @@ def test_skewed_gaussian_function(): def test_cauchy_function(): ctr, hgt, wid = 50, 5, 10 - - xs = np.arange(1, 100) + xs = np.arange(1, 100, 1.) ys = cauchy_function(xs, ctr, hgt, wid) assert np.all(ys) +def test_triangle_function(): + + ctr, hgt, wid = 50, 5, 10 + xs = np.arange(1, 100, 1.) + ys = triangle_function(xs, ctr, hgt, wid) + + assert np.any(ys) + ## Aperiodic functions def test_expo_function(): off, knee, exp = 10, 5, 2 - xs = np.arange(1, 100) + xs = np.arange(1, 100, 1.) ys = expo_function(xs, off, knee, exp) assert np.all(ys) @@ -74,7 +79,7 @@ def test_expo_nk_function(): off, exp = 10, 2 - xs = np.arange(1, 100) + xs = np.arange(1, 100, 1.) ys = expo_nk_function(xs, off, exp) assert np.all(ys) @@ -106,7 +111,7 @@ def test_linear_function(): off, sl = 10, 2 - xs = np.arange(1, 100) + xs = np.arange(1, 100, 1.) ys = linear_function(xs, off, sl) assert np.all(ys) @@ -120,7 +125,7 @@ def test_quadratic_function(): off, sl, curve = 10, 3, 2 - xs = np.arange(1, 100) + xs = np.arange(1, 100, 1.) ys = quadratic_function(xs, off, sl, curve) assert np.all(ys)