summaryrefslogtreecommitdiff
path: root/tests/test_parameters.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/test_parameters.py')
-rw-r--r--tests/test_parameters.py889
1 files changed, 574 insertions, 315 deletions
diff --git a/tests/test_parameters.py b/tests/test_parameters.py
index 67f6c41..37cad6f 100644
--- a/tests/test_parameters.py
+++ b/tests/test_parameters.py
@@ -1,319 +1,578 @@
+"""Tests for the Parameters class."""
+
+
from copy import copy, deepcopy
import pickle
-import unittest
+import asteval
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
-
-
-class TestParameters(unittest.TestCase):
-
- def setUp(self):
- self.params = Parameters()
- self.params.add_many(('a', 1., True, None, None, None),
- ('b', 2., True, None, None, None),
- ('c', 3., True, None, None, '2. * a'))
-
- def test_expr_was_evaluated(self):
- self.params.update_constraints()
- assert_almost_equal(self.params['c'].value,
- 2 * self.params['a'].value)
-
- def test_copy(self):
- # check simple Parameters.copy() does not fail
- # on non-trivial Parameters
- p1 = Parameters()
- p1.add('t', 2.0, min=0.0, max=5.0)
- p1.add('x', 10.0)
- p1.add('y', expr='x*t + sqrt(t)/3.0')
-
- p2 = p1.copy()
- assert isinstance(p2, Parameters)
- assert 't' in p2
- assert 'y' in p2
- assert p2['t'].max < 6.0
- assert np.isinf(p2['x'].max) and p2['x'].max > 0
- assert np.isinf(p2['x'].min) and p2['x'].min < 0
- assert 'sqrt(t)' in p2['y'].expr
- assert p2._asteval is not None
- assert p2._asteval.symtable is not None
- assert (p2['y'].value > 20) and (p2['y'].value < 21)
-
- def test_copy_function(self):
- # check copy(Parameters) does not fail
- p1 = Parameters()
- p1.add('t', 2.0, min=0.0, max=5.0)
- p1.add('x', 10.0)
- p1.add('y', expr='x*t + sqrt(t)/3.0')
-
- p2 = copy(p1)
- assert isinstance(p2, Parameters)
-
- # change the 'x' value in the original
- p1['x'].value = 4.0
-
- assert p2['x'].value > 9.8
- assert p2['x'].value < 10.2
- assert np.isinf(p2['x'].max) and p2['x'].max > 0
-
- assert 't' in p2
- assert 'y' in p2
- assert p2['t'].max < 6.0
-
- assert np.isinf(p2['x'].min) and p2['x'].min < 0
- assert 'sqrt(t)' in p2['y'].expr
- assert p2._asteval is not None
- assert p2._asteval.symtable is not None
- assert (p2['y'].value > 20) and (p2['y'].value < 21)
-
- assert p1['y'].value < 10
-
- def test_deepcopy(self):
- # check that a simple copy works
- b = deepcopy(self.params)
- assert_(self.params == b)
-
- # check that we can add a symbol to the interpreter
- self.params['b'].expr = 'sin(1)'
- self.params['b'].value = 10
- assert_almost_equal(self.params['b'].value, np.sin(1))
- assert_almost_equal(self.params._asteval.symtable['b'], np.sin(1))
-
- # check that the symbols in the interpreter are still the same after
- # deepcopying
- b = deepcopy(self.params)
-
- unique_symbols_params = self.params._asteval.user_defined_symbols()
- unique_symbols_b = self.params._asteval.user_defined_symbols()
- assert_(unique_symbols_b == unique_symbols_params)
- for unique_symbol in unique_symbols_b:
- if self.params._asteval.symtable[unique_symbol] is np.nan:
- continue
-
- assert_(self.params._asteval.symtable[unique_symbol]
- ==
- b._asteval.symtable[unique_symbol])
-
- def test_add_many_params(self):
- # test that we can add many parameters, but only parameters are added.
- a = Parameter('a', 1)
- b = Parameter('b', 2)
-
- p = Parameters()
- p.add_many(a, b)
-
- assert_(list(p.keys()) == ['a', 'b'])
-
- def test_expr_and_constraints_GH265(self):
- # test that parameters are reevaluated if they have bounds and expr
- # see GH265
- p = Parameters()
-
- p['a'] = Parameter('a', 10, True)
- p['b'] = Parameter('b', 10, True, 0, 20)
-
- assert_equal(p['b'].min, 0)
- assert_equal(p['b'].max, 20)
-
- p['a'].expr = '2 * b'
- assert_almost_equal(p['a'].value, 20)
-
- p['b'].value = 15
- assert_almost_equal(p['b'].value, 15)
- assert_almost_equal(p['a'].value, 30)
-
- p['b'].value = 30
- assert_almost_equal(p['b'].value, 20)
- assert_almost_equal(p['a'].value, 40)
-
- def test_pickle_parameter(self):
- # test that we can pickle a Parameter
- p = Parameter('a', 10, True, 0, 1)
- pkl = pickle.dumps(p)
-
- q = pickle.loads(pkl)
-
- assert_(p == q)
-
- def test_pickle_parameters(self):
- # test that we can pickle a Parameters object
- p = Parameters()
- p.add('a', 10, True, 0, 100)
- p.add('b', 10, True, 0, 100, 'a * sin(1)')
- p.update_constraints()
- p._asteval.symtable['abc'] = '2 * 3.142'
-
- pkl = pickle.dumps(p, -1)
- q = pickle.loads(pkl)
-
- q.update_constraints()
- assert_(p == q)
- assert_(p is not q)
-
- # now test if the asteval machinery survived
- assert_(q._asteval.symtable['abc'] == '2 * 3.142')
-
- # check that unpickling of Parameters is not affected by expr that
- # refer to Parameter that are added later on. In the following
- # example var_0.expr refers to var_1, which is a Parameter later
- # on in the Parameters OrderedDict.
- p = Parameters()
- p.add('var_0', value=1)
- p.add('var_1', value=2)
- p['var_0'].expr = 'var_1'
- pkl = pickle.dumps(p)
- q = pickle.loads(pkl)
-
- def test_params_usersyms(self):
- # test passing usersymes to Parameters()
- def myfun(x):
- return x**3
-
- params = Parameters(usersyms={"myfun": myfun})
- params.add("a", value=2.3)
- params.add("b", expr="myfun(a)")
-
- xx = np.linspace(0, 1, 10)
- yy = 3 * xx + np.random.normal(scale=0.002, size=len(xx))
-
- model = Model(lambda x, a: a * x)
- result = model.fit(yy, params=params, x=xx)
- assert_(np.isclose(result.params['a'].value, 3.0, rtol=0.025))
- assert_(result.nfev > 3)
- assert_(result.nfev < 300)
-
- def test_set_symtable(self):
- # test that we use Parameter.set(value=XXX) and have
- # that new value be used in constraint expressions
- pars = Parameters()
- pars.add('x', value=1.0)
- pars.add('y', expr='x + 1')
-
- assert_(np.isclose(pars['y'].value, 2.0))
- pars['x'].set(value=3.0)
- assert_(np.isclose(pars['y'].value, 4.0))
-
- def test_dumps_loads_parameters(self):
- # test that we can dumps() and then loads() a Parameters
- pars = Parameters()
- pars.add('x', value=1.0)
- pars.add('y', value=2.0)
- pars['x'].expr = 'y / 2.0'
-
- dumps = pars.dumps()
-
- newpars = Parameters().loads(dumps)
- newpars['y'].value = 100.0
- assert_(np.isclose(newpars['x'].value, 50.0))
-
- def test_isclose(self):
- assert_(np.isclose(1., 1+1e-5, atol=1e-4, rtol=0))
- assert_(not np.isclose(1., 1+1e-5, atol=1e-6, rtol=0))
- assert_(np.isclose(1e10, 1.00001e10, rtol=1e-5, atol=1e-8))
- assert_(not np.isclose(0, np.inf))
- assert_(not np.isclose(-np.inf, np.inf))
- assert_(np.isclose(np.inf, np.inf))
- assert_(not np.isclose(np.nan, np.nan))
-
- def test_expr_with_bounds(self):
- "test an expression with bounds, without value"
- pars = Parameters()
- pars.add('c1', value=0.2)
- pars.add('c2', value=0.2)
- pars.add('c3', value=0.2)
- pars.add('csum', value=0.8)
- # this should not raise TypeError:
- pars.add('c4', expr='csum-c1-c2-c3', min=0, max=1)
- assert_(np.isclose(pars['c4'].value, 0.2))
-
- def test_invalid_expr_exceptions(self):
- "test if an exception is raised for invalid expressions (GH486)"""
- p1 = Parameters()
- p1.add('t', 2.0, min=0.0, max=5.0)
- p1.add('x', 10.0)
- with self.assertRaises(SyntaxError):
- p1.add('y', expr='x*t + sqrt(t)/')
- assert len(p1['y']._expr_eval.error) > 0
- p1.add('y', expr='x*t + sqrt(t)/3.0')
- p1['y'].set(expr='x*3.0 + t**2')
- assert 'x*3' in p1['y'].expr
- assert len(p1['y']._expr_eval.error) == 0
- with self.assertRaises(SyntaxError):
- p1['y'].set(expr='t+')
- assert len(p1['y']._expr_eval.error) > 0
- assert_almost_equal(p1['y'].value, 34.0)
-
- def test_eval(self):
- # check that eval() works with usersyms and parameter values
- def myfun(x):
- return 2.0 * x
- p = Parameters(usersyms={"myfun": myfun})
- p.add("a", value=4.0)
- p.add("b", value=3.0)
- assert_almost_equal(p.eval("myfun(2.0) * a"), 16)
- assert_almost_equal(p.eval("b / myfun(3.0)"), 0.5)
-
- def test_params_html_table(self):
- p1 = Parameters()
- p1.add('t', 2.0, min=0.0, max=5.0)
- p1.add('x', 0.0, )
-
- html = params_html_table(p1)
- self.assertIsInstance(html, str)
-
- def test_add_params_expr_outoforder(self):
- params1 = Parameters()
- params1.add("a", value=1.0)
-
- params2 = Parameters()
- params2.add("b", value=1.0)
- params2.add("c", value=2.0)
- params2['b'].expr = 'c/2'
-
- params = params1 + params2
- assert 'b' in params
- assert_almost_equal(params['b'].value, 1.0)
-
- def test_params_prints(self):
- params = Parameters()
- params.add("a", value=1.0, vary=True)
- params.add("b", value=8.5, min=0, vary=True)
- params.add("c", expr='a + sqrt(b)')
-
- repr_full = params.pretty_repr()
- repr_one = params.pretty_repr(oneline=True)
-
- out = []
- for key, val in params.items():
- out.append("%s: %s" % (key, repr(val)))
- out = '\n'.join(out)
-
- assert repr_full.count('\n') > 4
- assert repr_one.count('\n') < 2
- 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)
+from numpy.testing import assert_allclose
+import pytest
+
+import lmfit
+
+
+@pytest.fixture
+def parameters():
+ """Initialize a Parameters class for tests."""
+ pars = lmfit.Parameters()
+ pars.add(lmfit.Parameter(name='a', value=10.0, vary=True, min=-100.0,
+ max=100.0, expr=None, brute_step=5.0,
+ user_data=1))
+ pars.add(lmfit.Parameter(name='b', value=0.0, vary=True, min=-250.0,
+ max=250.0, expr="2.0*a", brute_step=25.0,
+ user_data=2.5))
+ exp_attr_values_A = ('a', 10.0, True, -100.0, 100.0, None, 5.0, 1)
+ exp_attr_values_B = ('b', 20.0, False, -250.0, 250.0, "2.0*a", 25.0, 2.5)
+ assert_parameter_attributes(pars['a'], exp_attr_values_A)
+ assert_parameter_attributes(pars['b'], exp_attr_values_B)
+ return pars, exp_attr_values_A, exp_attr_values_B
+
+
+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
+
+
+def test_check_ast_errors():
+ """Assert that an exception is raised upon AST errors."""
+ pars = lmfit.Parameters()
+
+ msg = r"at expr='<_ast.Module object at"
+ with pytest.raises(NameError, match=msg):
+ pars.add('par1', expr='2.0*par2')
+
+
+def test_parameters_init():
+ """Test for initialization of the Parameters class"""
+ ast_int = asteval.Interpreter()
+ pars = lmfit.Parameters(asteval=ast_int, usersyms={'test': np.sin})
+ assert pars._asteval == ast_int
+ assert 'test' in pars._asteval.symtable
+
+
+def test_parameters_copy(parameters):
+ """Tests for copying a Parameters class; all use the __deepcopy__ method."""
+ pars, exp_attr_values_A, exp_attr_values_B = parameters
+
+ copy_pars = copy(pars)
+ pars_copy = pars.copy()
+ pars__copy__ = pars.__copy__()
+
+ pars['a'].set(value=100)
+
+ for copied in [copy_pars, pars_copy, pars__copy__]:
+ assert isinstance(copied, lmfit.Parameters)
+ assert copied != pars
+ assert copied._asteval is not None
+ assert copied._asteval.symtable is not None
+ assert_parameter_attributes(copied['a'], exp_attr_values_A)
+ assert_parameter_attributes(copied['b'], exp_attr_values_B)
+
+
+def test_parameters_deepcopy(parameters):
+ """Tests for deepcopy of a Parameters class."""
+ pars, _, _ = parameters
+
+ deepcopy_pars = deepcopy(pars)
+ assert isinstance(deepcopy_pars, lmfit.Parameters)
+ assert deepcopy_pars == pars
+
+ # check that we can add a symbol to the interpreter
+ pars['b'].expr = 'sin(1)'
+ pars['b'].value = 10
+ assert_allclose(pars['b'].value, np.sin(1))
+ assert_allclose(pars._asteval.symtable['b'], np.sin(1))
+
+ # check that the symbols in the interpreter are still the same after
+ # deepcopying
+ pars, exp_attr_values_A, exp_attr_values_B = parameters
+ deepcopy_pars = deepcopy(pars)
+
+ unique_symbols_pars = pars._asteval.user_defined_symbols()
+ unique_symbols_copied = deepcopy_pars._asteval.user_defined_symbols()
+ assert unique_symbols_copied == unique_symbols_pars
+
+ for unique_symbol in unique_symbols_copied:
+ if pars._asteval.symtable[unique_symbol] is not np.nan:
+ assert (pars._asteval.symtable[unique_symbol] ==
+ deepcopy_pars._asteval.symtable[unique_symbol])
+
+
+def test_parameters_update(parameters):
+ """Tests for updating a Parameters class."""
+ pars, exp_attr_values_A, exp_attr_values_B = parameters
+
+ msg = r"'test' is not a Parameters object"
+ with pytest.raises(ValueError, match=msg):
+ pars.update('test')
+
+ pars2 = lmfit.Parameters()
+ pars2.add(lmfit.Parameter(name='c', value=7.0, vary=True, min=-70.0,
+ max=70.0, expr=None, brute_step=0.7,
+ user_data=7))
+ exp_attr_values_C = ('c', 7.0, True, -70.0, 70.0, None, 0.7, 7)
+
+ pars_updated = pars.update(pars2)
+
+ assert_parameter_attributes(pars_updated['a'], exp_attr_values_A)
+ assert_parameter_attributes(pars_updated['b'], exp_attr_values_B)
+ assert_parameter_attributes(pars_updated['c'], exp_attr_values_C)
+
+
+def test_parameters__setitem__(parameters):
+ """Tests for __setitem__ method of a Parameters class."""
+ pars, _, exp_attr_values_B = parameters
+
+ msg = r"'10' is not a valid Parameters name"
+ with pytest.raises(KeyError, match=msg):
+ pars.__setitem__('10', None)
+
+ msg = r"'not_a_parameter' is not a Parameter"
+ with pytest.raises(ValueError, match=msg):
+ pars.__setitem__('a', 'not_a_parameter')
+
+ par = lmfit.Parameter('b', value=10, min=-25.0, brute_step=1)
+ pars.__setitem__('b', par)
+
+ exp_attr_values_B = ('b', 10, True, -25.0, np.inf, None, 1, None)
+ assert_parameter_attributes(pars['b'], exp_attr_values_B)
+
+
+def test_parameters__add__(parameters):
+ """Test the __add__ magic method."""
+ pars, exp_attr_values_A, exp_attr_values_B = parameters
+
+ msg = r"'other' is not a Parameters object"
+ with pytest.raises(ValueError, match=msg):
+ _ = pars + 'other'
+
+ pars2 = lmfit.Parameters()
+ pars2.add_many(('c', 1., True, None, None, None),
+ ('d', 2., True, None, None, None))
+ exp_attr_values_C = ('c', 1, True, -np.inf, np.inf, None, None, None)
+ exp_attr_values_D = ('d', 2, True, -np.inf, np.inf, None, None, None)
+
+ pars_added = pars + pars2
+
+ assert_parameter_attributes(pars_added['a'], exp_attr_values_A)
+ assert_parameter_attributes(pars_added['b'], exp_attr_values_B)
+ assert_parameter_attributes(pars_added['c'], exp_attr_values_C)
+ assert_parameter_attributes(pars_added['d'], exp_attr_values_D)
+
+
+def test_parameters__iadd__(parameters):
+ """Test the __iadd__ magic method."""
+ pars, exp_attr_values_A, exp_attr_values_B = parameters
+
+ msg = r"'other' is not a Parameters object"
+ with pytest.raises(ValueError, match=msg):
+ pars += 'other'
+
+ pars2 = lmfit.Parameters()
+ pars2.add_many(('c', 1., True, None, None, None),
+ ('d', 2., True, None, None, None))
+ exp_attr_values_C = ('c', 1, True, -np.inf, np.inf, None, None, None)
+ exp_attr_values_D = ('d', 2, True, -np.inf, np.inf, None, None, None)
+
+ pars += pars2
+
+ assert_parameter_attributes(pars['a'], exp_attr_values_A)
+ assert_parameter_attributes(pars['b'], exp_attr_values_B)
+ assert_parameter_attributes(pars['c'], exp_attr_values_C)
+ assert_parameter_attributes(pars['d'], exp_attr_values_D)
+
+
+def test_parameters_add_with_symtable():
+ """Regression test for GitHub Issue 607."""
+ pars1 = lmfit.Parameters()
+ pars1.add('a', value=1.0)
+
+ def half(x):
+ return 0.5*x
+
+ pars2 = lmfit.Parameters(usersyms={"half": half})
+ pars2.add("b", value=3.0)
+ pars2.add("c", expr="half(b)")
+
+ params = pars1 + pars2
+ assert_allclose(params['c'].value, 1.5)
+
+ params = pars2 + pars1
+ assert_allclose(params['c'].value, 1.5)
+
+ params = deepcopy(pars1)
+ params.update(pars2)
+ assert_allclose(params['c'].value, 1.5)
+
+ pars1 += pars2
+ assert_allclose(params['c'].value, 1.5)
+
+
+def test_parameters__array__(parameters):
+ """Test the __array__ magic method."""
+ pars, _, _ = parameters
+
+ assert_allclose(np.array(pars), np.array([10.0, 20.0]))
+
+
+def test_parameters__reduce__(parameters):
+ """Test the __reduce__ magic method."""
+ pars, _, _ = parameters
+ reduced = pars.__reduce__()
+
+ assert isinstance(reduced[2], dict)
+ assert 'unique_symbols' in reduced[2].keys()
+ assert reduced[2]['unique_symbols']['b'] == 20
+ assert 'params' in reduced[2].keys()
+ assert isinstance(reduced[2]['params'][0], lmfit.Parameter)
+
+
+def test_parameters__setstate__(parameters):
+ """Test the __setstate__ magic method."""
+ pars, exp_attr_values_A, exp_attr_values_B = parameters
+ reduced = pars.__reduce__()
+
+ pars_setstate = lmfit.Parameters()
+ pars_setstate.__setstate__(reduced[2])
+
+ assert isinstance(pars_setstate, lmfit.Parameters)
+ assert_parameter_attributes(pars_setstate['a'], exp_attr_values_A)
+ assert_parameter_attributes(pars_setstate['b'], exp_attr_values_B)
+
+
+def test_pickle_parameters():
+ """Test that we can pickle a Parameters object."""
+ p = lmfit.Parameters()
+ p.add('a', 10, True, 0, 100)
+ p.add('b', 10, True, 0, 100, 'a * sin(1)')
+ p.update_constraints()
+ p._asteval.symtable['abc'] = '2 * 3.142'
+
+ pkl = pickle.dumps(p, -1)
+ q = pickle.loads(pkl)
+
+ q.update_constraints()
+ assert p == q
+ assert p is not q
+
+ # now test if the asteval machinery survived
+ assert q._asteval.symtable['abc'] == '2 * 3.142'
+
+ # check that unpickling of Parameters is not affected by expr that
+ # refer to Parameter that are added later on. In the following
+ # example var_0.expr refers to var_1, which is a Parameter later
+ # on in the Parameters OrderedDict.
+ p = lmfit.Parameters()
+ p.add('var_0', value=1)
+ p.add('var_1', value=2)
+ p['var_0'].expr = 'var_1'
+ pkl = pickle.dumps(p)
+ q = pickle.loads(pkl)
+
+
+def test_parameters_eval(parameters):
+ """Test the eval method."""
+ pars, _, _ = parameters
+ evaluated = pars.eval('10.0*a+b')
+ assert_allclose(evaluated, 120)
+
+ # check that eval() works with usersyms and parameter values
+ def myfun(x):
+ return 2.0 * x
+
+ pars2 = lmfit.Parameters(usersyms={"myfun": myfun})
+ pars2.add('a', value=4.0)
+ pars2.add('b', value=3.0)
+ assert_allclose(pars2.eval('myfun(2.0) * a'), 16)
+ assert_allclose(pars2.eval('b / myfun(3.0)'), 0.5)
+
+
+def test_parameters_pretty_repr(parameters):
+ """Test the pretty_repr method."""
+ pars, _, _ = parameters
+ output = pars.pretty_repr()
+ output_oneline = pars.pretty_repr(oneline=True)
+
+ split_output = output.split('\n')
+ assert len(split_output) == 5
+ assert 'Parameters' in split_output[0]
+ assert "Parameter 'a'" in split_output[1]
+ assert "Parameter 'b'" in split_output[2]
+
+ oneliner = ("Parameters([('a', <Parameter 'a', value=10.0, "
+ "bounds=[-100.0:100.0], brute_step=5.0>), ('b', <Parameter "
+ "'b', value=20.0, bounds=[-250.0:250.0], expr='2.0*a', "
+ "brute_step=25.0>)])")
+ assert output_oneline == oneliner
+
+
+def test_parameters_pretty_print(parameters, capsys):
+ """Test the pretty_print method."""
+ pars, _, _ = parameters
+
+ # oneliner
+ pars.pretty_print(oneline=True)
+ captured = capsys.readouterr()
+ oneliner = ("Parameters([('a', <Parameter 'a', value=10.0, "
+ "bounds=[-100.0:100.0], brute_step=5.0>), ('b', <Parameter "
+ "'b', value=20.0, bounds=[-250.0:250.0], expr='2.0*a', "
+ "brute_step=25.0>)])")
+ assert oneliner in captured.out
+
+ # default
+ pars.pretty_print()
+ captured = capsys.readouterr()
+ captured_split = captured.out.split('\n')
+ assert len(captured_split) == 4
+ header = ('Name Value Min Max Stderr Vary '
+ 'Expr Brute_Step')
+ assert captured_split[0] == header
+
+ # specify columnwidth
+ pars.pretty_print(colwidth=12)
+ captured = capsys.readouterr()
+ captured_split = captured.out.split('\n')
+ header = ('Name Value Min Max Stderr '
+ ' Vary Expr Brute_Step')
+ assert captured_split[0] == header
+
+ # specify columns
+ pars['a'].stderr = 0.01
+ pars.pretty_print(columns=['value', 'min', 'max', 'stderr'])
+ captured = capsys.readouterr()
+ captured_split = captured.out.split('\n')
+ assert captured_split[0] == 'Name Value Min Max Stderr'
+ assert captured_split[1] == 'a 10 -100 100 0.01'
+ assert captured_split[2] == 'b 20 -250 250 None'
+
+ # specify fmt
+ pars.pretty_print(fmt='e', columns=['value', 'min', 'max'])
+ captured = capsys.readouterr()
+ captured_split = captured.out.split('\n')
+ assert captured_split[0] == 'Name Value Min Max'
+ assert captured_split[1] == 'a 1.0000e+01 -1.0000e+02 1.0000e+02'
+ assert captured_split[2] == 'b 2.0000e+01 -2.5000e+02 2.5000e+02'
+
+ # specify precision
+ pars.pretty_print(precision=2, fmt='e', columns=['value', 'min', 'max'])
+ captured = capsys.readouterr()
+ captured_split = captured.out.split('\n')
+ assert captured_split[0] == 'Name Value Min Max'
+ assert captured_split[1] == 'a 1.00e+01 -1.00e+02 1.00e+02'
+ assert captured_split[2] == 'b 2.00e+01 -2.50e+02 2.50e+02'
+
+
+def test_parameters__repr_html_(parameters):
+ """Test _repr_html method to generate HTML table for Parameters class."""
+ pars, _, _ = parameters
+ repr_html = pars._repr_html_()
+
+ assert isinstance(repr_html, str)
+ assert '<table><tr><th> name </th><th> value </th>' in repr_html
+
+
+def test_parameters_add():
+ """Tests for adding a Parameter to the Parameters class."""
+ pars = lmfit.Parameters()
+ pars_from_par = lmfit.Parameters()
+
+ pars.add('a')
+ exp_attr_values_A = ('a', -np.inf, True, -np.inf, np.inf, None, None, None)
+ assert_parameter_attributes(pars['a'], exp_attr_values_A)
+
+ pars_from_par.add(lmfit.Parameter('a'))
+ assert pars_from_par == pars
+
+ pars.add('b', value=1, vary=False, min=-5.0, max=5.0, brute_step=0.1)
+ exp_attr_values_B = ('b', 1.0, False, -5.0, 5.0, None, 0.1, None)
+ assert_parameter_attributes(pars['b'], exp_attr_values_B)
+
+ pars_from_par.add(lmfit.Parameter('b', value=1, vary=False, min=-5.0,
+ max=5.0, brute_step=0.1))
+ assert pars_from_par == pars
+
+
+def test_add_params_expr_outoforder():
+ """Regression test for GitHub Issue 560."""
+ params1 = lmfit.Parameters()
+ params1.add("a", value=1.0)
+
+ params2 = lmfit.Parameters()
+ params2.add("b", value=1.0)
+ params2.add("c", value=2.0)
+ params2['b'].expr = 'c/2'
+
+ params = params1 + params2
+ assert 'b' in params
+ assert_allclose(params['b'].value, 1.0)
+
+
+def test_parameters_add_many():
+ """Tests for add_many method."""
+ a = lmfit.Parameter('a', 1)
+ b = lmfit.Parameter('b', 2)
+
+ par = lmfit.Parameters()
+ par.add_many(a, b)
+
+ par_with_tuples = lmfit.Parameters()
+ par_with_tuples.add_many(('a', 1), ('b', 2))
+
+ assert list(par.keys()) == ['a', 'b']
+ assert par == par_with_tuples
+
+
+def test_parameters_valuesdict(parameters):
+ """Test for valuesdict method."""
+ pars, _, _ = parameters
+ vals_dict = pars.valuesdict()
+
+ assert isinstance(vals_dict, dict)
+ assert_allclose(vals_dict['a'], pars['a'].value)
+ assert_allclose(vals_dict['b'], pars['b'].value)
+
+
+def test_dumps_loads_parameters(parameters):
+ """Test for dumps and loads methods for a Parameters class."""
+ pars, _, _ = parameters
+
+ dumps = pars.dumps()
+ assert isinstance(dumps, str)
+ newpars = lmfit.Parameters().loads(dumps)
+ assert newpars == pars
+
+ newpars['a'].value = 100.0
+ assert_allclose(newpars['b'].value, 200.0)
+
+
+def test_dump_load_parameters(parameters):
+ """Test for dump and load methods for a Parameters class."""
+ pars, _, _ = parameters
+
+ with open('parameters.sav', 'w') as outfile:
+ pars.dump(outfile)
+
+ with open('parameters.sav') as infile:
+ newpars = pars.load(infile)
+
+ assert newpars == pars
+ newpars['a'].value = 100.0
+ assert_allclose(newpars['b'].value, 200.0)
+
+
+def test_dumps_loads_parameters_usersyms():
+ """Test for dumps/loads methods for a Parameters class with usersyms."""
+ def half(x):
+ return 0.5*x
+
+ pars = lmfit.Parameters(usersyms={"half": half, 'my_func': np.sqrt})
+ pars.add(lmfit.Parameter(name='a', value=9.0, min=-100.0, max=100.0))
+ pars.add(lmfit.Parameter(name='b', value=100.0, min=-250.0, max=250.0))
+ pars.add("c", expr="half(b) + my_func(a)")
+
+ dumps = pars.dumps()
+ assert isinstance(dumps, str)
+ assert '"half": {' in dumps
+ assert '"my_func": {' in dumps
+
+ newpars = lmfit.Parameters().loads(dumps)
+ assert 'half' in newpars._asteval.symtable
+ assert 'my_func' in newpars._asteval.symtable
+ assert_allclose(newpars['a'].value, 9.0)
+ assert_allclose(newpars['b'].value, 100.0)
+
+ # within the py.test environment the encoding of the function 'half' does
+ # not work correctly as it is changed from <function half at 0x?????????>"
+ # to "<function test_dumps_loads_parameters_usersyms.<locals>.half at 0x?????????>
+ # This result in the "importer" to be set to None and the final "decode4js"
+ # does not do the correct thing.
+ #
+ # Of note, this is only an issue within the py.test framework and it DOES
+ # work correctly in a normal Python interpreter. Also, it isn't an issue
+ # when DILL is used, so in that case the two asserts below will pass.
+ if lmfit.jsonutils.HAS_DILL:
+ assert newpars == pars
+ assert_allclose(newpars['c'].value, 53.0)
+
+
+def test_parameters_expr_and_constraints():
+ """Regression tests for GitHub Issue #265. Test that parameters are re-
+ evaluated if they have bounds and expr.
+
+ """
+ p = lmfit.Parameters()
+ p.add(lmfit.Parameter('a', 10, True))
+ p.add(lmfit.Parameter('b', 10, True, 0, 20))
+
+ assert_allclose(p['b'].min, 0)
+ assert_allclose(p['b'].max, 20)
+
+ p['a'].expr = '2 * b'
+ assert_allclose(p['a'].value, 20)
+
+ p['b'].value = 15
+ assert_allclose(p['b'].value, 15)
+ assert_allclose(p['a'].value, 30)
+
+ p['b'].value = 30
+ assert_allclose(p['b'].value, 20)
+ assert_allclose(p['a'].value, 40)
+
+
+def test_parameters_usersyms():
+ """Test for passing usersyms to Parameters()."""
+ def myfun(x):
+ return x**3
+
+ params = lmfit.Parameters(usersyms={"myfun": myfun})
+ params.add("a", value=2.3)
+ params.add("b", expr="myfun(a)")
+
+ np.random.seed(2020)
+ xx = np.linspace(0, 1, 10)
+ yy = 3 * xx + np.random.normal(scale=0.002, size=xx.size)
+
+ model = lmfit.Model(lambda x, a: a * x)
+ result = model.fit(yy, params=params, x=xx)
+ assert_allclose(result.params['a'].value, 3.0, rtol=1e-3)
+ assert (result.nfev > 3 and result.nfev < 300)
+
+
+def test_parameters_expr_with_bounds():
+ """Test Parameters using an expression with bounds, without value."""
+ pars = lmfit.Parameters()
+ pars.add('c1', value=0.2)
+ pars.add('c2', value=0.2)
+ pars.add('c3', value=0.2)
+ pars.add('csum', value=0.8)
+
+ # this should not raise TypeError:
+ pars.add('c4', expr='csum-c1-c2-c3', min=0, max=1)
+ assert_allclose(pars['c4'].value, 0.2)
+
+
+def test_invalid_expr_exceptions():
+ """Regression test for GitHub Issue #486: check that an exception is
+ raised for invalid expressions.
+
+ """
+ p1 = lmfit.Parameters()
+ p1.add('t', 2.0, min=0.0, max=5.0)
+ p1.add('x', 10.0)
+
+ with pytest.raises(SyntaxError):
+ p1.add('y', expr='x*t + sqrt(t)/')
+ assert len(p1['y']._expr_eval.error) > 0
+
+ p1.add('y', expr='x*t + sqrt(t)/3.0')
+ p1['y'].set(expr='x*3.0 + t**2')
+ assert 'x*3' in p1['y'].expr
+ assert len(p1['y']._expr_eval.error) == 0
+
+ with pytest.raises(SyntaxError):
+ p1['y'].set(expr='t+')
+ assert len(p1['y']._expr_eval.error) > 0
+ assert_allclose(p1['y'].value, 34.0)