#!/usr/bin/env python
# -*- coding: utf-8 -*-
# DOI: 10.5281/zenodo.15102100
# Full license can be found in License.md
#
# DISTRIBUTION STATEMENT A: Approved for public release. Distribution is
# unlimited.
# -----------------------------------------------------------------------------
"""Tests for functions in `utils.distributions`."""
import numpy as np
import unittest
from pyIntensityFeatures.utils import distributions as dist
[docs]
class TestCalcQuadratic(unittest.TestCase):
"""Tests for `calc_quadratic`."""
[docs]
def setUp(self):
"""Set up the test runs."""
# Retrieved quadratic values and results from:
# https://www.mathsisfun.com/algebra/quadratic-equation.html
self.a = 5.0
self.b = 6.0
self.c = 1.0
self.x = [-0.2, -1]
self.y = 0.0
return
[docs]
def tearDown(self):
"""Tear down the test environment."""
del self.a, self.b, self.c, self.x, self.y
return
[docs]
def test_float_calc(self):
"""Test success for a float calculation."""
# Cycle through the x-values
for val in self.x:
with self.subTest(x=val):
# Solve the quadratic equation
out = dist.calc_quadratic(val, self.c, self.b, self.a)
self.assertAlmostEqual(out, self.y)
return
[docs]
def test_array_calc(self):
"""Test success for an array calculation."""
# Cast the x-values
self.x = np.array(self.x)
# Solve the quadratic equation
out = dist.calc_quadratic(self.x, self.c, self.b, self.a)
self.assertEqual(out.shape, self.x.shape)
self.assertTrue(np.all(abs(self.y - out) < 1.0e-7),
msg="Unexpected y values: {:}".format(out))
return
[docs]
class TestSingleGauss(unittest.TestCase):
"""Tests for the single-peaked Gaussian functions."""
[docs]
def setUp(self):
"""Set up the test runs."""
self.const = 1.0
self.a = 0.001
self.b = 0.01
vals = np.random.normal(5.0, 0.3, 10000) + self.const
self.num, bins = np.histogram(vals, 50)
self.mu = vals.mean()
self.sigma = vals.std()
self.loc = (bins[:-1] + (bins[1:] - bins[:-1]) / 2.0).astype(float)
self.num = self.num.astype(float)
self.amp = self.num.max()
self.max_mae = 20.0
self.out = None
return
[docs]
def tearDown(self):
"""Tear down the test environment."""
del self.const, self.num, self.mu, self.sigma, self.amp, self.loc
del self.out, self.max_mae, self.a, self.b
return
[docs]
def test_gauss(self):
"""Test the `gauss` function values."""
# Get the Gaussian solution for the location
self.out = dist.gauss(self.loc, self.amp, self.mu, self.sigma,
self.const)
# Evaluate the mean absolute error
self.assertLess(abs(np.mean(self.out - self.num)), self.max_mae)
return
[docs]
def test_single_gauss_quad(self):
"""Test the `single_gauss_quad` function values."""
# Add a quadratic background to the values and adjust the amplitude
self.num += dist.calc_quadratic(self.loc, self.const, self.b, self.a)
self.amp = self.num.max()
# Get the Gaussian solution for the location
self.out = dist.single_gauss_quad(self.loc, self.const, self.b, self.a,
self.amp, self.mu, self.sigma)
# Evaluate the mean absolute error
self.assertLess(abs(np.mean(self.out - self.num)), self.max_mae)
return
[docs]
class TestMultGauss(unittest.TestCase):
"""Tests for the multi-peaked Gaussian functions."""
[docs]
def setUp(self):
"""Set up the test runs."""
self.const = 1.0
self.a = 0.001
self.b = 0.01
self.npeaks = 1
self.mu = []
self.sigma = []
self.amp = []
self.loc = None
self.num = None
self.max_mae = 20.0
self.out = None
return
[docs]
def tearDown(self):
"""Tear down the test environment."""
del self.const, self.num, self.mu, self.sigma, self.amp, self.loc
del self.out, self.max_mae, self.a, self.b, self.npeaks
return
[docs]
def set_gaussian(self, add_quad=False):
"""Set the Gaussian values to test against.
Parameters
----------
add_quad : bool
Add a quadratic background if True (default=False)
"""
# Reset the output arrays
self.mu = []
self.sigma = []
self.amp = []
# Get the points at each peak
vals = []
for peak_mu in range(self.npeaks):
peak_vals = np.random.normal(peak_mu, 0.3, 1000)
peak_num, _ = np.histogram(peak_vals, 50)
# Set the peak-specific values that won't change upon addition
self.mu.append(np.mean(peak_vals))
self.sigma.append(np.std(peak_vals))
# Add the peak-specific values to the output
vals.extend(list(peak_vals))
# Calculate the total locations and values
self.num, bins = np.histogram(vals, 50)
self.loc = (bins[:-1] + (bins[1:] - bins[:-1]) / 2.0).astype(float)
self.num = self.num.astype(float)
# If desired, add a quadratic background
if add_quad:
self.num += dist.calc_quadratic(self.loc, 0.0, self.b, self.a)
# Get the amplitues at each peak location
for x in self.mu:
self.amp.append(self.num[abs(self.loc - x).argmin()])
return
[docs]
def test_mult_gauss(self):
"""Test the `mult_gauss` function values for 1, 2, 3, and 4 peaks."""
# Cycle through float and array-like input
for iloc in [0, slice(None, None)]:
# Cycle through the number of peaks
for self.npeaks in range(4):
with self.subTest(npeaks=self.npeaks, xind=iloc):
# Update the comparitive data
self.set_gaussian(add_quad=False)
# Get the Gaussian solution for the location
self.out = dist.mult_gauss(self.loc[iloc], self.amp,
self.mu, self.sigma, self.const)
# Evaluate the mean absolute error
self.assertLess(abs(np.mean(self.out - self.num[iloc])),
self.max_mae)
return
[docs]
def test_mult_gauss_quad(self):
"""Test `mult_gauss_quad` for 1, 2, 3, and 4 Gaussian peaks."""
# Cycle through the number of peaks
for self.npeaks in np.arange(1, 5, 1):
# Initalize the comparitive data
self.set_gaussian(add_quad=True)
# Initalize the parameter input
params = [self.const, self.b, self.a]
for i, amplitude in enumerate(self.amp):
params.append(amplitude)
params.append(self.mu[i])
params.append(self.sigma[i])
with self.subTest(npeaks=self.npeaks):
# Get the Gaussian solution for the location
self.out = dist.mult_gauss_quad(self.loc, *params)
# Evaluate the mean absolute error
self.assertLess(abs(np.mean(self.out - self.num)), self.max_mae)
return
[docs]
def test_mult_gauss_quad_bad_amplitude(self):
"""Test `mult_gauss_quad` raises an error for an extra input."""
args = [1.0, [self.const, self.b, self.a, 1.0, 2.0, 3.0, 1.0]]
self.assertRaisesRegex(ValueError,
"Unexpected number of input parameters",
dist.mult_gauss_quad, *args)
return