summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/test_NIST_Strd.py2
-rw-r--r--tests/test_ampgo.py6
-rw-r--r--tests/test_confidence.py62
-rw-r--r--tests/test_covariance_matrix.py1
-rw-r--r--tests/test_custom_independentvar.py2
-rw-r--r--tests/test_jsonutils.py7
-rw-r--r--tests/test_least_squares.py51
-rw-r--r--tests/test_lineshapes_models.py1
-rw-r--r--tests/test_model.py29
-rw-r--r--tests/test_nose.py102
-rw-r--r--tests/test_parameter.py514
-rw-r--r--tests/test_parameters.py24
-rw-r--r--tests/test_saveload.py9
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__), '..',