diff options
Diffstat (limited to 'tests')
-rw-r--r-- | tests/test_NIST_Strd.py | 2 | ||||
-rw-r--r-- | tests/test_ampgo.py | 6 | ||||
-rw-r--r-- | tests/test_confidence.py | 62 | ||||
-rw-r--r-- | tests/test_covariance_matrix.py | 1 | ||||
-rw-r--r-- | tests/test_custom_independentvar.py | 2 | ||||
-rw-r--r-- | tests/test_jsonutils.py | 7 | ||||
-rw-r--r-- | tests/test_least_squares.py | 51 | ||||
-rw-r--r-- | tests/test_lineshapes_models.py | 1 | ||||
-rw-r--r-- | tests/test_model.py | 29 | ||||
-rw-r--r-- | tests/test_nose.py | 102 | ||||
-rw-r--r-- | tests/test_parameter.py | 514 | ||||
-rw-r--r-- | tests/test_parameters.py | 24 | ||||
-rw-r--r-- | tests/test_saveload.py | 9 |
13 files changed, 705 insertions, 105 deletions
diff --git a/tests/test_NIST_Strd.py b/tests/test_NIST_Strd.py index 00ad869..0753319 100644 --- a/tests/test_NIST_Strd.py +++ b/tests/test_NIST_Strd.py @@ -1,5 +1,3 @@ -from __future__ import print_function - import math from optparse import OptionParser diff --git a/tests/test_ampgo.py b/tests/test_ampgo.py index 3ca9691..83cf0dd 100644 --- a/tests/test_ampgo.py +++ b/tests/test_ampgo.py @@ -58,7 +58,11 @@ def test_ampgo_maxfunevals(minimizer_Alpine02): def test_ampgo_local_solver(minimizer_Alpine02): """Test AMPGO algorithm with local solver.""" kws = {'local': 'Nelder-Mead'} - out = minimizer_Alpine02.minimize(method='ampgo', **kws) + + msg = r'Method Nelder-Mead cannot handle constraints nor bounds' + with pytest.warns(RuntimeWarning, match=msg): + out = minimizer_Alpine02.minimize(method='ampgo', **kws) + out_x = np.array([out.params['x0'].value, out.params['x1'].value]) assert 'ampgo' and 'Nelder-Mead' in out.method diff --git a/tests/test_confidence.py b/tests/test_confidence.py index f090e24..804cc02 100644 --- a/tests/test_confidence.py +++ b/tests/test_confidence.py @@ -2,6 +2,7 @@ import numpy as np from numpy.testing import assert_allclose import pytest +from scipy.stats import f import lmfit from lmfit_testutils import assert_paramval @@ -12,7 +13,7 @@ def data(): """Generate synthetic data.""" x = np.linspace(0.3, 10, 100) np.random.seed(0) - y = 1.0/(0.1*x) + 2.0 + 0.1*np.random.randn(x.size) + y = 1.0 / (0.1 * x) + 2.0 + 0.1 * np.random.randn(x.size) return (x, y) @@ -26,7 +27,7 @@ def pars(): def residual(params, x, data): """Define objective function for the minimization.""" - return data - 1.0/(params['a']*x) + params['b'] + return data - 1.0 / (params['a'] * x) + params['b'] @pytest.mark.parametrize("verbose", [False, True]) @@ -54,6 +55,40 @@ def test_confidence_leastsq(data, pars, verbose, capsys): assert 'Calculating CI for' in captured.out +def test_confidence_pnames(data, pars): + """Test if pnames works as expected.""" + minimizer = lmfit.Minimizer(residual, pars, fcn_args=(data)) + out = minimizer.leastsq() + + assert_paramval(out.params['a'], 0.1, tol=0.1) + assert_paramval(out.params['b'], -2.0, tol=0.1) + + ci = lmfit.conf_interval(minimizer, out, p_names=['a']) + assert 'a' in ci + assert 'b' not in ci + + +def test_confidence_bounds_reached(data, pars): + """Check if conf_interval handles bounds correctly""" + + # Should work + pars['a'].max = 0.2 + minimizer = lmfit.Minimizer(residual, pars, fcn_args=(data)) + out = minimizer.leastsq() + out.params['a'].stderr = 1 + lmfit.conf_interval(minimizer, out, verbose=True) + + # Should warn + pars['b'].max = 2.03 + pars['b'].min = 1.97 + minimizer = lmfit.Minimizer(residual, pars, fcn_args=(data)) + out = minimizer.leastsq() + out.params['b'].stderr = 0.005 + out.params['a'].stderr = 0.01 + with pytest.warns(UserWarning, match="Bound reached"): + lmfit.conf_interval(minimizer, out, verbose=True) + + def test_confidence_sigma_vs_prob(data, pars): """Calculate confidence by specifying sigma or probability.""" minimizer = lmfit.Minimizer(residual, pars, fcn_args=(data)) @@ -61,8 +96,9 @@ def test_confidence_sigma_vs_prob(data, pars): ci_sigmas = lmfit.conf_interval(minimizer, out, sigmas=[1, 2, 3]) ci_1sigma = lmfit.conf_interval(minimizer, out, sigmas=[1]) - ci_probs = lmfit.conf_interval(minimizer, out, sigmas=[0.68269, 0.9545, - 0.9973]) + ci_probs = lmfit.conf_interval(minimizer, + out, + sigmas=[0.68269, 0.9545, 0.9973]) assert_allclose(ci_sigmas['a'][0][1], ci_probs['a'][0][1], rtol=0.01) assert_allclose(ci_sigmas['b'][2][1], ci_probs['b'][2][1], rtol=0.01) @@ -72,7 +108,9 @@ def test_confidence_sigma_vs_prob(data, pars): def test_confidence_exceptions(data, pars): """Make sure the proper exceptions are raised when needed.""" - minimizer = lmfit.Minimizer(residual, pars, calc_covar=False, + minimizer = lmfit.Minimizer(residual, + pars, + calc_covar=False, fcn_args=data) out = minimizer.minimize(method='nelder') out_lsq = minimizer.minimize(params=out.params, method='leastsq') @@ -144,3 +182,17 @@ def test_confidence_2d_limits(data, pars): assert_allclose(max(cx.ravel()), 0.02) assert_allclose(min(cy.ravel()), -4.0) assert_allclose(max(cy.ravel()), 1.0e-6) + + +def test_confidence_prob_func(data, pars): + """Test conf_interval with alternate prob_func.""" + minimizer = lmfit.Minimizer(residual, pars, fcn_args=data) + out = minimizer.minimize(method='leastsq') + + def my_f_compare(best_fit, new_fit): + nfree = best_fit.nfree + nfix = best_fit.nfree - new_fit.nfree + dchi = new_fit.chisqr / best_fit.chisqr - 1.0 + return f.cdf(dchi * nfree / nfix, nfix, nfree) + + lmfit.conf_interval(minimizer, out, sigmas=[1], prob_func=my_f_compare) diff --git a/tests/test_covariance_matrix.py b/tests/test_covariance_matrix.py index c9d581a..f5bc900 100644 --- a/tests/test_covariance_matrix.py +++ b/tests/test_covariance_matrix.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- import os import numpy as np diff --git a/tests/test_custom_independentvar.py b/tests/test_custom_independentvar.py index 0a22b6d..ae39963 100644 --- a/tests/test_custom_independentvar.py +++ b/tests/test_custom_independentvar.py @@ -4,7 +4,7 @@ from lmfit.lineshapes import gaussian from lmfit.models import Model -class Stepper(object): +class Stepper: def __init__(self, start, stop, npts): self.start = start self.stop = stop diff --git a/tests/test_jsonutils.py b/tests/test_jsonutils.py index 38b63e4..19d0225 100644 --- a/tests/test_jsonutils.py +++ b/tests/test_jsonutils.py @@ -1,12 +1,9 @@ """Tests for the JSON utilities.""" -# coding: utf-8 -# TODO: remove the line above when dropping Python 2.7 from types import BuiltinFunctionType, FunctionType import numpy as np import pytest from scipy.optimize import basinhopping -import six import lmfit from lmfit.jsonutils import decode4js, encode4js, find_importer, import_from @@ -21,12 +18,12 @@ def test_import_from(obj): (BuiltinFunctionType, FunctionType)) -objects = [('test_string', six.string_types), +objects = [('test_string', (str,)), (np.array([7.0]), np.ndarray), (np.array([1.0+2.0j]), np.ndarray), (123.456, np.float), (10, np.float), - (u'café', six.string_types), + ('café', (str,)), (10.0-5.0j, np.complex), (['a', 'b', 'c'], list), (('a', 'b', 'c'), tuple), diff --git a/tests/test_least_squares.py b/tests/test_least_squares.py index 778c204..b9aa64e 100644 --- a/tests/test_least_squares.py +++ b/tests/test_least_squares.py @@ -2,6 +2,8 @@ import numpy as np from numpy.testing import assert_allclose import pytest +from scipy.sparse import bsr_matrix +from scipy.sparse.linalg import aslinearoperator import lmfit from lmfit.models import VoigtModel @@ -116,3 +118,52 @@ def test_least_squares_solver_options(peakdata, capsys): assert 'Iteration' in captured.out assert 'final cost' in captured.out + + +def test_least_squares_jacobian_types(): + """Test support for Jacobian of all types supported by least_squares.""" + # Build function + # f(x, y) = (x - a)^2 + (y - b)^2 + np.random.seed(42) + a = np.random.normal(0, 1, 50) + np.random.seed(43) + b = np.random.normal(0, 1, 50) + + def f(params): + return (params['x'] - a)**2 + (params['y'] - b)**2 + + # Build analytic Jacobian functions with the different possible return types + # numpy.ndarray, scipy.sparse.spmatrix, scipy.sparse.linalg.LinearOperator + # J = [ 2x - 2a , 2y - 2b ] + def jac_array(params, *args, **kwargs): + return np.column_stack((2 * params[0] - 2 * a, 2 * params[1] - 2 * b)) + + def jac_sparse(params, *args, **kwargs): + return bsr_matrix(jac_array(params, *args, **kwargs)) + + def jac_operator(params, *args, **kwargs): + return aslinearoperator(jac_array(params, *args, **kwargs)) + # Build parameters + params = lmfit.Parameters() + params.add('x', value=0) + params.add('y', value=0) + # Solve model for numerical Jacobian and each analytic Jacobian function + result = lmfit.minimize(f, params, method='least_squares') + result_array = lmfit.minimize( + f, params, method='least_squares', + jac=jac_array) + result_sparse = lmfit.minimize( + f, params, method='least_squares', + jac=jac_sparse) + result_operator = lmfit.minimize( + f, params, method='least_squares', + jac=jac_operator) + # Check that all have uncertainties + assert result.errorbars + assert result_array.errorbars + assert result_sparse.errorbars + assert result_operator.errorbars + # Check that all have ~equal covariance matrix + assert_allclose(result.covar, result_array.covar) + assert_allclose(result.covar, result_sparse.covar) + assert_allclose(result.covar, result_operator.covar) diff --git a/tests/test_lineshapes_models.py b/tests/test_lineshapes_models.py index 4302bcd..9ad9ef1 100644 --- a/tests/test_lineshapes_models.py +++ b/tests/test_lineshapes_models.py @@ -5,7 +5,6 @@ import sys import numpy as np from numpy.testing import assert_allclose - import pytest from scipy.optimize import fsolve diff --git a/tests/test_model.py b/tests/test_model.py index 4518e58..ba9e8dc 100644 --- a/tests/test_model.py +++ b/tests/test_model.py @@ -42,7 +42,7 @@ def linear_func(x, a, b): return a*x+b -class CommonTests(object): +class CommonTests: # to be subclassed for testing predefined models def setUp(self): @@ -234,6 +234,9 @@ class CommonTests(object): class TestUserDefiniedModel(CommonTests, unittest.TestCase): # mainly aimed at checking that the API does what it says it does # and raises the right exceptions or warnings when things are not right + import six + if six.PY2: + from six import assertRaisesRegex def setUp(self): self.true_values = lambda: dict(amplitude=7.1, center=1.1, sigma=2.40) @@ -241,7 +244,7 @@ class TestUserDefiniedModel(CommonTests, unittest.TestCase): # return a fresh copy self.model_constructor = ( lambda *args, **kwargs: Model(gaussian, *args, **kwargs)) - super(TestUserDefiniedModel, self).setUp() + super().setUp() @property def x(self): @@ -641,7 +644,7 @@ class TestUserDefiniedModel(CommonTests, unittest.TestCase): result = lambda: mod.fit(y, params, x=x, nan_policy='raise') msg = ('NaN values detected in your input data or the output of your ' 'objective/model function - fitting algorithms cannot handle this!') - self.assertRaisesRegexp(ValueError, msg, result) + self.assertRaisesRegex(ValueError, msg, result) # with propagate, should get no error, but bad results result = mod.fit(y, params, x=x, nan_policy='propagate') @@ -684,7 +687,7 @@ class TestUserDefiniedModel(CommonTests, unittest.TestCase): nan_policy='raise') msg = 'The model function generated NaN values and the fit aborted!' - self.assertRaisesRegexp(ValueError, msg, result) + self.assertRaisesRegex(ValueError, msg, result) @pytest.mark.skipif(sys.version_info.major == 2, reason="cannot use wrapped functions with Python 2") @@ -715,7 +718,7 @@ class TestLinear(CommonTests, unittest.TestCase): self.true_values = lambda: dict(slope=5, intercept=2) self.guess = lambda: dict(slope=10, intercept=6) self.model_constructor = models.LinearModel - super(TestLinear, self).setUp() + super().setUp() class TestParabolic(CommonTests, unittest.TestCase): @@ -724,7 +727,7 @@ class TestParabolic(CommonTests, unittest.TestCase): self.true_values = lambda: dict(a=5, b=2, c=8) self.guess = lambda: dict(a=1, b=6, c=3) self.model_constructor = models.ParabolicModel - super(TestParabolic, self).setUp() + super().setUp() class TestPolynomialOrder2(CommonTests, unittest.TestCase): @@ -734,7 +737,7 @@ class TestPolynomialOrder2(CommonTests, unittest.TestCase): self.guess = lambda: dict(c1=1, c2=6, c0=3) self.model_constructor = models.PolynomialModel self.args = (2,) - super(TestPolynomialOrder2, self).setUp() + super().setUp() class TestPolynomialOrder3(CommonTests, unittest.TestCase): @@ -744,7 +747,7 @@ class TestPolynomialOrder3(CommonTests, unittest.TestCase): self.guess = lambda: dict(c3=1, c1=1, c2=6, c0=3) self.model_constructor = models.PolynomialModel self.args = (3,) - super(TestPolynomialOrder3, self).setUp() + super().setUp() class TestConstant(CommonTests, unittest.TestCase): @@ -752,7 +755,7 @@ class TestConstant(CommonTests, unittest.TestCase): self.true_values = lambda: dict(c=5) self.guess = lambda: dict(c=2) self.model_constructor = models.ConstantModel - super(TestConstant, self).setUp() + super().setUp() def check_skip_independent_vars(self): raise pytest.skip("ConstantModel has not independent_vars.") @@ -763,7 +766,7 @@ class TestPowerlaw(CommonTests, unittest.TestCase): self.true_values = lambda: dict(amplitude=5, exponent=3) self.guess = lambda: dict(amplitude=2, exponent=8) self.model_constructor = models.PowerLawModel - super(TestPowerlaw, self).setUp() + super().setUp() class TestExponential(CommonTests, unittest.TestCase): @@ -771,7 +774,7 @@ class TestExponential(CommonTests, unittest.TestCase): self.true_values = lambda: dict(amplitude=5, decay=3) self.guess = lambda: dict(amplitude=2, decay=8) self.model_constructor = models.ExponentialModel - super(TestExponential, self).setUp() + super().setUp() class TestComplexConstant(CommonTests, unittest.TestCase): @@ -779,7 +782,7 @@ class TestComplexConstant(CommonTests, unittest.TestCase): self.true_values = lambda: dict(re=5, im=5) self.guess = lambda: dict(re=2, im=2) self.model_constructor = models.ComplexConstantModel - super(TestComplexConstant, self).setUp() + super().setUp() class TestExpression(CommonTests, unittest.TestCase): @@ -789,7 +792,7 @@ class TestExpression(CommonTests, unittest.TestCase): self.expression = "off_c + amp_c * exp(-x/x0)" self.model_constructor = ( lambda *args, **kwargs: models.ExpressionModel(self.expression, *args, **kwargs)) - super(TestExpression, self).setUp() + super().setUp() def test_composite_with_expression(self): expression_model = models.ExpressionModel("exp(-x/x0)", name='exp') diff --git a/tests/test_nose.py b/tests/test_nose.py index 3ec4a22..d3b482a 100644 --- a/tests/test_nose.py +++ b/tests/test_nose.py @@ -5,13 +5,12 @@ from numpy import pi from numpy.testing import (assert_, assert_allclose, assert_almost_equal, assert_equal, dec) import pytest -from scipy.version import version as scipy_version from uncertainties import ufloat from lmfit import Minimizer, Parameters, minimize from lmfit.lineshapes import gaussian from lmfit.minimizer import (HAS_EMCEE, SCALAR_METHODS, MinimizerResult, - _lnpost, _nan_policy) + _nan_policy) def check(para, real_val, sig=3): @@ -341,7 +340,6 @@ class CommonMinimizerTest(unittest.TestCase): fit_params.add('shift', value=.10, min=0.0, max=0.2) fit_params.add('decay', value=6.e-3, min=0, max=0.1) self.fit_params = fit_params - self.mini = Minimizer(self.residual, fit_params, [self.x, self.data]) def residual(self, pars, x, data=None): @@ -395,10 +393,8 @@ class CommonMinimizerTest(unittest.TestCase): # the data returned by userfcn self.data[0] = np.nan - major, minor, _micro = scipy_version.split('.', 2) for method in SCALAR_METHODS: - if (method == 'differential_evolution' and int(major) > 0 and - int(minor) >= 2): + if method == 'differential_evolution': pytest.raises(RuntimeError, self.mini.scalar_minimize, SCALAR_METHODS[method]) else: @@ -443,40 +439,29 @@ class CommonMinimizerTest(unittest.TestCase): # test with emcee as method keyword argument if not HAS_EMCEE: return True - np.random.seed(123456) - out = self.mini.minimize(method='emcee', nwalkers=100, steps=200, + out = self.mini.minimize(method='emcee', + nwalkers=50, steps=200, burn=50, thin=10) assert out.method == 'emcee' - assert out.nfev == 100*200 + assert out.nfev == 50*200 check_paras(out.params, self.p_true, sig=3) - out_unweighted = self.mini.minimize(method='emcee', is_weighted=False) + out_unweighted = self.mini.minimize(method='emcee', + nwalkers=50, steps=200, + burn=50, thin=10, + is_weighted=False) assert out_unweighted.method == 'emcee' @dec.slow - def test_emcee_PT(self): - # test emcee with parallel tempering - if not HAS_EMCEE: - return True - - np.random.seed(123456) - self.mini.userfcn = residual_for_multiprocessing - out = self.mini.emcee(ntemps=4, nwalkers=50, steps=200, - burn=100, thin=10, workers=2) - - check_paras(out.params, self.p_true, sig=3) - - @dec.slow def test_emcee_multiprocessing(self): # test multiprocessing runs + raise pytest.skip("Pytest fails with multiprocessing") + pytest.importorskip("dill") if not HAS_EMCEE: return True - - np.random.seed(123456) - self.mini.userfcn = residual_for_multiprocessing - self.mini.emcee(steps=10, workers=4) + self.mini.emcee(steps=50, workers=4, nwalkers=20) def test_emcee_bounds_length(self): # the log-probability functions check if the parameters are @@ -513,22 +498,21 @@ class CommonMinimizerTest(unittest.TestCase): out = self.mini.emcee(nwalkers=100, steps=5) # can initialise with a chain self.mini.emcee(nwalkers=100, steps=1, pos=out.chain) - # can initialise with a correct subset of a chain - self.mini.emcee(nwalkers=100, steps=1, pos=out.chain[..., -1, :]) + self.mini.emcee(nwalkers=100, steps=1, pos=out.chain[-1, ...]) # but you can't initialise if the shape is wrong. pytest.raises(ValueError, self.mini.emcee, nwalkers=100, steps=1, - pos=out.chain[..., -1, :-1]) + pos=out.chain[-1, :-1, ...]) def test_emcee_reuse_sampler(self): if not HAS_EMCEE: return True - self.mini.emcee(nwalkers=100, steps=5) + self.mini.emcee(nwalkers=20, steps=25) # if you've run the sampler the Minimizer object should have a _lastpos # attribute @@ -536,7 +520,7 @@ class CommonMinimizerTest(unittest.TestCase): # now try and re-use sampler out2 = self.mini.emcee(steps=10, reuse_sampler=True) - assert_(out2.chain.shape[1] == 15) + assert_(out2.chain.shape == (35, 20, 4)) # you shouldn't be able to reuse the sampler if nvarys has changed. self.mini.params['amp'].vary = False @@ -563,12 +547,10 @@ class CommonMinimizerTest(unittest.TestCase): # calculate the log-likelihood value bounds = np.array([(par.min, par.max) for par in result.params.values()]) - val2 = _lnpost(fvars, - self.residual, - result.params, - result.var_names, - bounds, - userargs=(self.x, self.data)) + + val2 = self.mini._lnprob(fvars, self.residual, result.params, + result.var_names, bounds, + userargs=(self.x, self.data)) assert_almost_equal(-0.5 * val, val2) @@ -585,32 +567,8 @@ class CommonMinimizerTest(unittest.TestCase): assert_(isinstance(out.flatchain, DataFrame)) # check that we can access the chains via parameter name - assert_(out.flatchain['amp'].shape[0] == 80) - assert out.errorbars - assert_(np.isfinite(out.params['amp'].correl['period'])) - - # the lnprob array should be the same as the chain size - assert_(np.size(out.chain)//out.nvarys == np.size(out.lnprob)) - - # test chain output shapes - assert_(out.lnprob.shape == (10, (20-5+1)/2)) - assert_(out.chain.shape == (10, (20-5+1)/2, out.nvarys)) - assert_(out.flatchain.shape == (10*(20-5+1)/2, out.nvarys)) - - def test_emcee_PT_output(self): - # test mcmc output when using parallel tempering - if not HAS_EMCEE: - return True - try: - from pandas import DataFrame - except ImportError: - return True - out = self.mini.emcee(ntemps=6, nwalkers=10, steps=20, burn=5, thin=2) - assert_(isinstance(out, MinimizerResult)) - assert_(isinstance(out.flatchain, DataFrame)) - - # check that we can access the chains via parameter name - assert_(out.flatchain['amp'].shape[0] == 80) + # print( out.flatchain['amp'].shape[0], 200) + assert_(out.flatchain['amp'].shape[0] == 70) assert out.errorbars assert_(np.isfinite(out.params['amp'].correl['period'])) @@ -618,10 +576,10 @@ class CommonMinimizerTest(unittest.TestCase): assert_(np.size(out.chain)//out.nvarys == np.size(out.lnprob)) # test chain output shapes - assert_(out.lnprob.shape == (6, 10, (20-5+1)/2)) - assert_(out.chain.shape == (6, 10, (20-5+1)/2, out.nvarys)) - # Only the 0th temperature is returned - assert_(out.flatchain.shape == (10*(20-5+1)/2, out.nvarys)) + print(out.lnprob.shape, out.chain.shape, out.flatchain.shape) + assert_(out.lnprob.shape == (7, 10)) + assert_(out.chain.shape == (7, 10, 4)) + assert_(out.flatchain.shape == (70, 4)) @dec.slow def test_emcee_float(self): @@ -662,6 +620,14 @@ class CommonMinimizerTest(unittest.TestCase): assert_almost_equal(out.chain, out2.chain) + def test_emcee_ntemps(self): + # check for DeprecationWarning when using ntemps > 1 + if not HAS_EMCEE: + return True + + with pytest.raises(DeprecationWarning): + _ = self.mini.emcee(params=self.fit_params, ntemps=5) + def residual_for_multiprocessing(pars, x, data=None): # a residual function defined in the top level is needed for diff --git a/tests/test_parameter.py b/tests/test_parameter.py new file mode 100644 index 0000000..5c2e5c5 --- /dev/null +++ b/tests/test_parameter.py @@ -0,0 +1,514 @@ +"""Tests for the Parameter class.""" + +from math import trunc + +import numpy as np +from numpy.testing import assert_allclose +import pytest +import uncertainties as un + +import lmfit + + +@pytest.fixture +def parameter(): + """Initialize parameter for tests.""" + param = lmfit.Parameter(name='a', value=10.0, vary=True, min=-100.0, + max=100.0, expr=None, brute_step=5.0, user_data=1) + expected_attribute_values = ('a', 10.0, True, -100.0, 100.0, None, 5.0, 1) + assert_parameter_attributes(param, expected_attribute_values) + return param, expected_attribute_values + + +def assert_parameter_attributes(par, expected): + """Assert that parameter attributes have the expected values.""" + par_attr_values = (par.name, par._val, par.vary, par.min, par.max, + par._expr, par.brute_step, par.user_data) + assert par_attr_values == expected + + +in_out = [(lmfit.Parameter(name='a'), # set name + ('a', -np.inf, True, -np.inf, np.inf, None, None, None)), + (lmfit.Parameter(name='a', value=10.0), # set value + ('a', 10.0, True, -np.inf, np.inf, None, None, None)), + (lmfit.Parameter(name='a', vary=False), # fix parameter, set vary to False + ('a', -np.inf, False, -np.inf, np.inf, None, None, None)), + (lmfit.Parameter(name='a', min=-10.0), # set lower bound, value reset to min + ('a', -10.0, True, -10.0, np.inf, None, None, None)), + (lmfit.Parameter(name='a', value=-5.0, min=-10.0), # set lower bound + ('a', -5.0, True, -10.0, np.inf, None, None, None)), + (lmfit.Parameter(name='a', max=10.0), # set upper bound + ('a', -np.inf, True, -np.inf, 10.0, None, None, None)), + (lmfit.Parameter(name='a', value=25.0, max=10.0), # set upper bound, value reset + ('a', 10.0, True, -np.inf, 10.0, None, None, None)), + (lmfit.Parameter(name='a', expr="2.0*10.0"), # set expression, vary becomes False + ('a', -np.inf, True, -np.inf, np.inf, '2.0*10.0', None, None)), + (lmfit.Parameter(name='a', brute_step=0.1), # set brute_step + ('a', -np.inf, True, -np.inf, np.inf, None, 0.1, None)), + (lmfit.Parameter(name='a', user_data={'b': {}}), # set user_data + ('a', -np.inf, True, -np.inf, np.inf, None, None, {'b': {}}))] + + +@pytest.mark.parametrize('par, attr_values', in_out) +def test_initialize_Parameter(par, attr_values): + """Test the initialization of the Parameter class.""" + assert_parameter_attributes(par, attr_values) + + # check for other default attributes + for attribute in ['_expr', '_expr_ast', '_expr_eval', '_expr_deps', + '_delay_asteval', 'stderr', 'correl', 'from_internal', + '_val']: + assert hasattr(par, attribute) + + +def test_Parameter_no_name(): + """Test for Parameter name, now required positional argument.""" + msg = r"missing 1 required positional argument: 'name'" + with pytest.raises(TypeError, match=msg): + lmfit.Parameter() + + +def test_init_bounds(): + """Tests to make sure that initial bounds are consistent. + + Only for specific cases not tested above with the initializations of the + Parameter class. + + """ + # test 1: min > max; should swap min and max + par = lmfit.Parameter(name='a', value=0.0, min=10.0, max=-10.0) + assert par.min == -10.0 + assert par.max == 10.0 + + # test 2: min == max; should raise a ValueError + msg = r"Parameter 'a' has min == max" + with pytest.raises(ValueError, match=msg): + par = lmfit.Parameter(name='a', value=0.0, min=10.0, max=10.0) + + # FIXME: ideally this should be impossible to happen ever.... + # perhaps we should add a setter method for MIN and MAX as well? + # test 3: max or min is equal to None + par.min = None + par._init_bounds() + assert par.min == -np.inf + + par.max = None + par._init_bounds() + assert par.max == np.inf + + +def test_parameter_set_value(parameter): + """Test the Parameter.set() function with value.""" + par, initial_attribute_values = parameter + + par.set(value=None) # nothing should change + assert_parameter_attributes(par, initial_attribute_values) + + par.set(value=5.0) + changed_attribute_values = ('a', 5.0, True, -100.0, 100.0, None, 5.0, 1) + assert_parameter_attributes(par, changed_attribute_values) + + +def test_parameter_set_vary(parameter): + """Test the Parameter.set() function with vary.""" + par, initial_attribute_values = parameter + + par.set(vary=None) # nothing should change + assert_parameter_attributes(par, initial_attribute_values) + + par.set(vary=False) + changed_attribute_values = ('a', 10.0, False, -100.0, 100.0, None, 5.0, 1) + assert_parameter_attributes(par, changed_attribute_values) + + +def test_parameter_set_min(parameter): + """Test the Parameter.set() function with min.""" + par, initial_attribute_values = parameter + + par.set(min=None) # nothing should change + assert_parameter_attributes(par, initial_attribute_values) + + par.set(min=-50.0) + changed_attribute_values = ('a', 10.0, True, -50.0, 100.0, None, 5.0, 1) + assert_parameter_attributes(par, changed_attribute_values) + + +def test_parameter_set_max(parameter): + """Test the Parameter.set() function with max.""" + par, initial_attribute_values = parameter + + par.set(max=None) # nothing should change + assert_parameter_attributes(par, initial_attribute_values) + + par.set(max=50.0) + changed_attribute_values = ('a', 10.0, True, -100.0, 50.0, None, 5.0, 1) + assert_parameter_attributes(par, changed_attribute_values) + + +def test_parameter_set_expr(parameter): + """Test the Parameter.set() function with expr. + + Of note, this only tests for setting/removal of the expression; nothing + else gets evaluated here.... More specific tests will be present in the + Parameters class. + + """ + par, _ = parameter + + par.set(expr='2.0*50.0') # setting an expression, vary --> False + changed_attribute_values = ('a', 10.0, False, -100.0, 100.0, '2.0*50.0', + 5.0, 1) + assert_parameter_attributes(par, changed_attribute_values) + + par.set(expr=None) # nothing should change + assert_parameter_attributes(par, changed_attribute_values) + + par.set(expr='') # should remove the expression + changed_attribute_values = ('a', 10.0, False, -100.0, 100.0, None, 5.0, 1) + assert_parameter_attributes(par, changed_attribute_values) + + +def test_parameter_set_brute_step(parameter): + """Test the Parameter.set() function with brute_step.""" + par, initial_attribute_values = parameter + + par.set(brute_step=None) # nothing should change + assert_parameter_attributes(par, initial_attribute_values) + + par.set(brute_step=0.0) # brute_step set to None + changed_attribute_values = ('a', 10.0, True, -100.0, 100.0, None, None, 1) + assert_parameter_attributes(par, changed_attribute_values) + + par.set(brute_step=1.0) + changed_attribute_values = ('a', 10.0, True, -100.0, 100.0, None, 1.0, 1) + assert_parameter_attributes(par, changed_attribute_values) + + +def test_getstate(parameter): + """Test for the __getstate__ method.""" + par, _ = parameter + assert par.__getstate__() == ('a', 10.0, True, None, -100.0, 100.0, 5.0, + None, None, 10, 1) + + +def test_setstate(parameter): + """Test for the __setstate__ method.""" + par, initial_attribute_values = parameter + state = par.__getstate__() + + par_new = lmfit.Parameter('new') + attributes_new = ('new', -np.inf, True, -np.inf, np.inf, None, None, None) + assert_parameter_attributes(par_new, attributes_new) + + par_new.__setstate__(state) + assert_parameter_attributes(par_new, initial_attribute_values) + + +def test_repr(): + """Tests for the __repr__ method.""" + par = lmfit.Parameter(name='test', value=10.0, min=0.0, max=20.0) + assert par.__repr__() == "<Parameter 'test', value=10.0, bounds=[0.0:20.0]>" + + par = lmfit.Parameter(name='test', value=10.0, vary=False) + assert par.__repr__() == "<Parameter 'test', value=10.0 (fixed), bounds=[-inf:inf]>" + + par.set(vary=True) + par.stderr = 0.1 + assert par.__repr__() == "<Parameter 'test', value=10.0 +/- 0.1, bounds=[-inf:inf]>" + + par = lmfit.Parameter(name='test', expr='10.0*2.5') + assert par.__repr__() == "<Parameter 'test', value=-inf, bounds=[-inf:inf], expr='10.0*2.5'>" + + par = lmfit.Parameter(name='test', brute_step=0.1) + assert par.__repr__() == "<Parameter 'test', value=-inf, bounds=[-inf:inf], brute_step=0.1>" + + +def test_setup_bounds_and_scale_gradient_methods(): + """Tests for the setup_bounds and scale_gradient methods. + + Make use of the MINUIT-style transformation to obtain the the Parameter + values and scaling factor for the gradient. + See: https://lmfit.github.io/lmfit-py/bounds.html + + """ + # situation 1: no bounds + par_no_bounds = lmfit.Parameter('no_bounds', value=10.0) + assert_allclose(par_no_bounds.setup_bounds(), 10.0) + assert_allclose(par_no_bounds.scale_gradient(par_no_bounds.value), 1.0) + + # situation 2: no bounds, min/max set to None after creating the parameter + # TODO: ideally this should never happen; perhaps use a setter here + par_no_bounds = lmfit.Parameter('no_bounds', value=10.0) + par_no_bounds.min = None + par_no_bounds.max = None + assert_allclose(par_no_bounds.setup_bounds(), 10.0) + assert_allclose(par_no_bounds.scale_gradient(par_no_bounds.value), 1.0) + + # situation 3: upper bound + par_upper_bound = lmfit.Parameter('upper_bound', value=10.0, max=25.0) + assert_allclose(par_upper_bound.setup_bounds(), 15.968719422671311) + assert_allclose(par_upper_bound.scale_gradient(par_upper_bound.value), + -0.99503719, rtol=1.e-6) + + # situation 4: lower bound + par_lower_bound = lmfit.Parameter('upper_bound', value=10.0, min=-25.0) + assert_allclose(par_lower_bound.setup_bounds(), 35.98610843) + assert_allclose(par_lower_bound.scale_gradient(par_lower_bound.value), + 0.995037, rtol=1.e-6) + + # situation 5: both lower and upper bounds + par_both_bounds = lmfit.Parameter('both_bounds', value=10.0, min=-25.0, + max=25.0) + assert_allclose(par_both_bounds.setup_bounds(), 0.4115168460674879) + assert_allclose(par_both_bounds.scale_gradient(par_both_bounds.value), + -20.976788, rtol=1.e-6) + + +def test__getval(parameter): + """Test _getval function.""" + par, _ = parameter + + # test uncertainties.core.Variable in _getval [deprecated] + par.set(value=un.ufloat(5.0, 0.2)) + with pytest.warns(FutureWarning, match='removed in the next release'): + val = par.value + assert_allclose(val, 5.0) + + +def test_value_setter(parameter): + """Tests for the value setter.""" + par, initial_attribute_values = parameter + assert_parameter_attributes(par, initial_attribute_values) + + par.set(value=200.0) # above maximum + assert_allclose(par.value, 100.0) + + par.set(value=-200.0) # below minimum + assert_allclose(par.value, -100.0) + + +# TODO: add tests for setter/getter methods for VALUE, EXPR + + +# Tests for magic methods of the Parameter class +def test__array__(parameter): + """Test the __array__ magic method.""" + par, _ = parameter + assert np.array(par) == np.array(10.0) + + +def test__str__(parameter): + """Test the __str__ magic method.""" + par, _ = parameter + assert str(par) == "<Parameter 'a', value=10.0, bounds=[-100.0:100.0], brute_step=5.0>" + + +def test__abs__(parameter): + """Test the __abs__ magic method.""" + par, _ = parameter + assert_allclose(abs(par), 10.0) + par.set(value=-10.0) + assert_allclose(abs(par), 10.0) + + +def test__neg__(parameter): + """Test the __neg__ magic method.""" + par, _ = parameter + assert_allclose(-par, -10.0) + par.set(value=-10.0) + assert_allclose(-par, 10.0) + + +def test__pos__(parameter): + """Test the __pos__ magic method.""" + par, _ = parameter + assert_allclose(+par, 10.0) + par.set(value=-10.0) + assert_allclose(+par, -10.0) + + +def test__bool__(parameter): + """Test the __bool__ magic method.""" + par, _ = parameter + assert bool(par) + + +def test__int__(parameter): + """Test the __int__ magic method.""" + par, _ = parameter + assert isinstance(int(par), int) + assert_allclose(int(par), 10) + + +def test__float__(parameter): + """Test the __float__ magic method.""" + par, _ = parameter + par.set(value=5) + assert isinstance(float(par), float) + assert_allclose(float(par), 5.0) + + +def test__trunc__(parameter): + """Test the __trunc__ magic method.""" + par, _ = parameter + par.set(value=10.5) + assert isinstance(trunc(par), int) + assert_allclose(trunc(par), 10) + + +def test__add__(parameter): + """Test the __add__ magic method.""" + par, _ = parameter + assert_allclose(par + 5.25, 15.25) + + +def test__sub__(parameter): + """Test the __sub__ magic method.""" + par, _ = parameter + assert_allclose(par - 5.25, 4.75) + + +def test__truediv__(parameter): + """Test the __truediv__ magic method.""" + par, _ = parameter + assert_allclose(par / 1.25, 8.0) + + +def test__floordiv__(parameter): + """Test the __floordiv__ magic method.""" + par, _ = parameter + par.set(value=5) + assert_allclose(par // 2, 2) + + +def test__divmod__(parameter): + """Test the __divmod__ magic method.""" + par, _ = parameter + assert_allclose(divmod(par, 3), (3, 1)) + + +def test__mod__(parameter): + """Test the __mod__ magic method.""" + par, _ = parameter + assert_allclose(par % 2, 0) + assert_allclose(par % 3, 1) + + +def test__mul__(parameter): + """Test the __mul__ magic method.""" + par, _ = parameter + assert_allclose(par * 2.5, 25.0) + assert_allclose(par * -0.1, -1.0) + + +def test__pow__(parameter): + """Test the __pow__ magic method.""" + par, _ = parameter + assert_allclose(par ** 0.5, 3.16227766) + assert_allclose(par ** 4, 1e4) + + +def test__gt__(parameter): + """Test the __gt__ magic method.""" + par, _ = parameter + assert 11 > par + assert not 10 > par + + +def test__ge__(parameter): + """Test the __ge__ magic method.""" + par, _ = parameter + assert 11 >= par + assert 10 >= par + assert not 9 >= par + + +def test__le__(parameter): + """Test the __le__ magic method.""" + par, _ = parameter + assert 9 <= par + assert 10 <= par + assert not 11 <= par + + +def test__lt__(parameter): + """Test the __lt__ magic method.""" + par, _ = parameter + assert 9 < par + assert not 10 < par + + +def test__eq__(parameter): + """Test the __eq__ magic method.""" + par, _ = parameter + assert 10 == par + assert not 9 == par + + +def test__ne__(parameter): + """Test the __ne__ magic method.""" + par, _ = parameter + assert 9 != par + assert not 10 != par + + +def test__radd__(parameter): + """Test the __radd__ magic method.""" + par, _ = parameter + assert_allclose(5.25 + par, 15.25) + + +def test__rtruediv__(parameter): + """Test the __rtruediv__ magic method.""" + par, _ = parameter + assert_allclose(1.25 / par, 0.125) + + +def test__rdivmod__(parameter): + """Test the __rdivmod__ magic method.""" + par, _ = parameter + assert_allclose(divmod(3, par), (0, 3)) + + +def test__rfloordiv__(parameter): + """Test the __rfloordiv__ magic method.""" + par, _ = parameter + assert_allclose(2 // par, 0) + assert_allclose(20 // par, 2) + + +def test__rmod__(parameter): + """Test the __rmod__ magic method.""" + par, _ = parameter + assert_allclose(2 % par, 2) + assert_allclose(25 % par, 5) + + +def test__rmul__(parameter): + """Test the __rmul__ magic method.""" + par, _ = parameter + assert_allclose(2.5 * par, 25.0) + assert_allclose(-0.1 * par, -1.0) + + +def test__rpow__(parameter): + """Test the __rpow__ magic method.""" + par, _ = parameter + assert_allclose(0.5 ** par, 0.0009765625) + assert_allclose(4 ** par, 1048576) + + +def test__rsub__(parameter): + """Test the __rsub__ magic method.""" + par, _ = parameter + assert_allclose(5.25 - par, -4.75) + + +def test_isParameter(parameter): + """Test function to check whether something is a Paramter [deprecated].""" + # TODO: this function isn't used anywhere in the codebase; useful at all? + par, _ = parameter + assert lmfit.parameter.isParameter(par) + assert not lmfit.parameter.isParameter('test') + with pytest.warns(FutureWarning, match='removed in the next release'): + lmfit.parameter.isParameter(par) diff --git a/tests/test_parameters.py b/tests/test_parameters.py index a5fcc0e..67f6c41 100644 --- a/tests/test_parameters.py +++ b/tests/test_parameters.py @@ -1,5 +1,3 @@ -from __future__ import print_function - from copy import copy, deepcopy import pickle import unittest @@ -8,7 +6,6 @@ import numpy as np from numpy.testing import assert_, assert_almost_equal, assert_equal from lmfit import Model, Parameter, Parameters - from lmfit.printfuncs import params_html_table @@ -299,3 +296,24 @@ class TestParameters(unittest.TestCase): assert len(repr_full) > 150 assert len(repr_one) > 150 assert len(out) > 150 + + def test_add_with_symtable(self): + pars1 = Parameters() + pars1.add("a", value=1.0, vary=True) + + def half(x): + return 0.5*x + + pars2 = Parameters(usersyms={"half": half}) + pars2.add("b", value=3.0) + pars2.add("c", expr="half(b)") + + params = pars1 + pars2 + assert_almost_equal(params['c'].value, 1.5) + + params = pars2 + pars1 + assert_almost_equal(params['c'].value, 1.5) + + params = deepcopy(pars1) + params.update(pars2) + assert_almost_equal(params['c'].value, 1.5) diff --git a/tests/test_saveload.py b/tests/test_saveload.py index 0ec66cf..cb4e026 100644 --- a/tests/test_saveload.py +++ b/tests/test_saveload.py @@ -6,13 +6,12 @@ import numpy as np from numpy.testing import assert_allclose import pytest -import lmfit.jsonutils from lmfit import Parameters -from lmfit.model import (load_model, load_modelresult, save_model, - save_modelresult, Model, ModelResult) -from lmfit.models import ExponentialModel, GaussianModel, VoigtModel +import lmfit.jsonutils from lmfit.lineshapes import gaussian, lorentzian - +from lmfit.model import (Model, ModelResult, load_model, load_modelresult, + save_model, save_modelresult) +from lmfit.models import ExponentialModel, GaussianModel, VoigtModel from lmfit_testutils import assert_between, assert_param_between y, x = np.loadtxt(os.path.join(os.path.dirname(__file__), '..', |