Repository: lmfit/lmfit-py

Issue:
Incorrect value in MinimizerResult.init_values
#### First Time Issue Code
Yes, I read the instructions and I am sure this is a GitHub Issue.

#### Description
Using the [simple example] from the documentation and printing `result.init_values` (or `result.init_vals`) as in the MWE below reveals an incorrect initial value for `amp`. It should be `10` but it is `10.954451150103322`.

[simple example]: https://lmfit.github.io/lmfit-py/parameters.html#simple-example

###### A Minimal, Complete, and Verifiable example
```python
import numpy as np

from lmfit import Minimizer, Parameters, report_fit

# create data to be fitted
x = np.linspace(0, 15, 301)
np.random.seed(2021)
data = (5.0 * np.sin(2.0*x - 0.1) * np.exp(-x*x*0.025) +
        np.random.normal(size=x.size, scale=0.2))


# define objective function: returns the array to be minimized
def fcn2min(params, x, data):
    """Model a decaying sine wave and subtract data."""
    amp = params['amp']
    shift = params['shift']
    omega = params['omega']
    decay = params['decay']
    model = amp * np.sin(x*omega + shift) * np.exp(-x*x*decay)
    return model - data


# create a set of Parameters
params = Parameters()
params.add('amp', value=10, min=0)
params.add('decay', value=0.1)
params.add('shift', value=0.0, min=-np.pi/2., max=np.pi/2.)
params.add('omega', value=3.0)

# do fit, here with the default leastsq algorithm
minner = Minimizer(fcn2min, params, fcn_args=(x, data))
result = minner.minimize()

# calculate final result
final = data + result.residual

# write error report
report_fit(result)

# now print the inital values
print(result.init_values)
```

This gives the output
```
[[Fit Statistics]]
    # fitting method   = leastsq
    # function evals   = 59
    # data points      = 301
    # variables        = 4
    chi-square         = 12.1867036
    reduced chi-square = 0.04103267
    Akaike info crit   = -957.236198
    Bayesian info crit = -942.407756
[[Variables]]
    amp:    5.03087926 +/- 0.04005805 (0.80%) (init = 10)
    decay:  0.02495454 +/- 4.5395e-04 (1.82%) (init = 0.1)
    shift: -0.10264934 +/- 0.01022298 (9.96%) (init = 0)
    omega:  2.00026304 +/- 0.00326184 (0.16%) (init = 3)
[[Correlations]] (unreported correlations are < 0.100)
    C(shift, omega) = -0.785
    C(amp, decay)   = 0.584
    C(amp, shift)   = -0.118
{'amp': 10.954451150103322, 'decay': 0.1, 'shift': 0.0, 'omega': 3.0}
```

`report_fit` correctly reports an initial value of `10` for `amp` but `result.init_values` does not.

###### Version information
```
Python: 3.9.13 | packaged by conda-forge | (main, May 27 2022, 16:50:36) [MSC v.1929 64 bit (AMD64)]

lmfit: 1.0.3, scipy: 1.9.3, numpy: 1.23.4,asteval: 0.9.27, uncertainties: 3.1.7
```



Comments:

Patch:
diff --git a/doc/constraints.rst b/doc/constraints.rst
index dbe439e3..cc82bb98 100644
--- a/doc/constraints.rst
+++ b/doc/constraints.rst
@@ -13,7 +13,7 @@ highly desirable to place mathematical constraints on parameter values.
 For example, one might want to require that two Gaussian peaks have the
 same width, or have amplitudes that are constrained to add to some value.
 Of course, one could rewrite the objective or model function to place such
-requirements, but this is somewhat error prone, and limits the flexibility
+requirements, but this is somewhat error-prone, and limits the flexibility
 so that exploring constraints becomes laborious.
 
 To simplify the setting of constraints, Parameters can be assigned a
diff --git a/doc/fitting.rst b/doc/fitting.rst
index 60e66548..50a05c8c 100644
--- a/doc/fitting.rst
+++ b/doc/fitting.rst
@@ -129,7 +129,7 @@ Choosing Different Fitting Methods
 By default, the `Levenberg-Marquardt
 <https://en.wikipedia.org/wiki/Levenberg-Marquardt_algorithm>`_ algorithm is
 used for fitting. While often criticized, including the fact it finds a
-*local* minima, this approach has some distinct advantages. These include
+*local* minimum, this approach has some distinct advantages. These include
 being fast, and well-behaved for most curve-fitting needs, and making it
 easy to estimate uncertainties for and correlations between pairs of fit
 variables, as discussed in :ref:`fit-results-label`.
@@ -476,7 +476,7 @@ be used to abort a fit.
    :type  resid:  numpy.ndarray
    :param args:  Positional arguments. Must match ``args`` argument to :func:`minimize`
    :param kws:   Keyword arguments. Must match ``kws`` argument to :func:`minimize`
-   :return:      Residual array (generally ``data-model``) to be minimized in the least-squares sense.
+   :return:      Iteration abort flag.
    :rtype:    None for normal behavior, any value like ``True`` to abort the fit.
 
 
diff --git a/doc/model.rst b/doc/model.rst
index b38254cd..3400cc15 100644
--- a/doc/model.rst
+++ b/doc/model.rst
@@ -378,7 +378,7 @@ Parameters if the supplied default value was a valid number (but not
 .. jupyter-execute::
 
     def decay2(t, tau, N=10, check_positive=False):
-        if check_small:
+        if check_positive:
             arg = abs(t)/max(1.e-9, abs(tau))
         else:
             arg = t/tau
diff --git a/lmfit/lineshapes.py b/lmfit/lineshapes.py
index 761d7e2a..7ecdc42d 100644
--- a/lmfit/lineshapes.py
+++ b/lmfit/lineshapes.py
@@ -1,7 +1,7 @@
 """Basic model lineshapes and distribution functions."""
 
-from numpy import (arctan, copysign, cos, exp, isclose, isnan, log, log1p, pi,
-                   real, sin, sqrt, where)
+from numpy import (arctan, copysign, cos, exp, isclose, isnan, log, log1p,
+                   maximum, minimum, pi, real, sin, sqrt, where)
 from scipy.special import betaln as betalnfcn
 from scipy.special import erf, erfc
 from scipy.special import gamma as gamfcn
@@ -431,13 +431,11 @@ def step(x, amplitude=1.0, center=0.0, sigma=1.0, form='linear'):
     if form == 'erf':
         out = 0.5*(1 + erf(out))
     elif form == 'logistic':
-        out = (1. - 1./(1. + exp(out)))
+        out = 1. - 1./(1. + exp(out))
     elif form in ('atan', 'arctan'):
         out = 0.5 + arctan(out)/pi
     elif form == 'linear':
-        out = out + 0.5
-        out[out < 0] = 0.0
-        out[out > 1] = 1.0
+        out = minimum(1, maximum(0, out + 0.5))
     else:
         msg = (f"Invalid value ('{form}') for argument 'form'; should be one "
                "of 'erf', 'logistic', 'atan', 'arctan', or 'linear'.")
@@ -471,15 +469,11 @@ def rectangle(x, amplitude=1.0, center1=0.0, sigma1=1.0,
     if form == 'erf':
         out = 0.5*(erf(arg1) + erf(arg2))
     elif form == 'logistic':
-        out = (1. - 1./(1. + exp(arg1)) - 1./(1. + exp(arg2)))
+        out = 1. - 1./(1. + exp(arg1)) - 1./(1. + exp(arg2))
     elif form in ('atan', 'arctan'):
         out = (arctan(arg1) + arctan(arg2))/pi
     elif form == 'linear':
-        arg1[arg1 < 0] = 0.0
-        arg1[arg1 > 1] = 1.0
-        arg2[arg2 > 0] = 0.0
-        arg2[arg2 < -1] = -1.0
-        out = arg1 + arg2
+        out = 0.5*(minimum(1, maximum(-1, arg1)) + minimum(1, maximum(-1, arg2)))
     else:
         msg = (f"Invalid value ('{form}') for argument 'form'; should be one "
                "of 'erf', 'logistic', 'atan', 'arctan', or 'linear'.")
diff --git a/lmfit/minimizer.py b/lmfit/minimizer.py
index e9c3c0b7..11d874dc 100644
--- a/lmfit/minimizer.py
+++ b/lmfit/minimizer.py
@@ -710,6 +710,7 @@ class Minimizer:
         # and which are defined expressions.
         result.var_names = []  # note that this *does* belong to self...
         result.init_vals = []
+        result._init_vals_internal = []
         result.params.update_constraints()
         result.nfev = 0
         result.call_kws = {}
@@ -725,7 +726,8 @@ class Minimizer:
                 par.vary = False
             if par.vary:
                 result.var_names.append(name)
-                result.init_vals.append(par.setup_bounds())
+                result._init_vals_internal.append(par.setup_bounds())
+                result.init_vals.append(par.value)
 
             par.init_value = par.value
             if par.name is None:
@@ -953,11 +955,12 @@ class Minimizer:
         """
         result = self.prepare_fit(params=params)
         result.method = method
-        variables = result.init_vals
+        variables = result._init_vals_internal
         params = result.params
 
         self.set_max_nfev(max_nfev, 2000*(result.nvarys+1))
         fmin_kws = dict(method=method, options={'maxiter': 2*self.max_nfev})
+        # fmin_kws = dict(method=method, options={'maxfun': 2*self.max_nfev})
         fmin_kws.update(self.kws)
 
         if 'maxiter' in kws:
@@ -1661,7 +1664,7 @@ class Minimizer:
         result = self.prepare_fit(params=params)
         result.method = 'leastsq'
         result.nfev -= 2  # correct for "pre-fit" initialization/checks
-        variables = result.init_vals
+        variables = result._init_vals_internal
 
         # note we set the max number of function evaluations here, and send twice that
         # value to the solver so it essentially never stops on its own
@@ -1779,7 +1782,7 @@ class Minimizer:
         basinhopping_kws.update(self.kws)
         basinhopping_kws.update(kws)
 
-        x0 = result.init_vals
+        x0 = result._init_vals_internal
         result.call_kws = basinhopping_kws
         try:
             ret = scipy_basinhopping(self.penalty, x0, **basinhopping_kws)
@@ -2072,7 +2075,7 @@ class Minimizer:
         ampgo_kws.update(self.kws)
         ampgo_kws.update(kws)
 
-        values = result.init_vals
+        values = result._init_vals_internal
         result.method = f"ampgo, with {ampgo_kws['local']} as local solver"
         result.call_kws = ampgo_kws
         try:
@@ -2212,8 +2215,7 @@ class Minimizer:
         result.method = 'dual_annealing'
         self.set_max_nfev(max_nfev, 200000*(result.nvarys+1))
 
-        da_kws = dict(maxiter=1000, local_search_options={},
-                      initial_temp=5230.0, restart_temp_ratio=2e-05,
+        da_kws = dict(initial_temp=5230.0, restart_temp_ratio=2e-05,
                       visit=2.62, accept=-5.0, maxfun=2*self.max_nfev,
                       seed=None, no_local_search=False, callback=None,
                       x0=None)
diff --git a/lmfit/models.py b/lmfit/models.py
index 7c2d3d1d..090c2dce 100644
--- a/lmfit/models.py
+++ b/lmfit/models.py
@@ -1480,7 +1480,7 @@ class StepModel(Model):
         & f(x; A, \mu, \sigma, {\mathrm{form={}'linear{}'}})  & = A \min{[1, \max{(0, \alpha + 1/2)}]} \\
         & f(x; A, \mu, \sigma, {\mathrm{form={}'arctan{}'}})  & = A [1/2 + \arctan{(\alpha)}/{\pi}] \\
         & f(x; A, \mu, \sigma, {\mathrm{form={}'erf{}'}})     & = A [1 + {\operatorname{erf}}(\alpha)]/2 \\
-        & f(x; A, \mu, \sigma, {\mathrm{form={}'logistic{}'}})& = A [1 - \frac{1}{1 + e^{\alpha}} ]
+        & f(x; A, \mu, \sigma, {\mathrm{form={}'logistic{}'}})& = A \left[1 - \frac{1}{1 + e^{\alpha}} \right]
         \end{eqnarray*}
 
     where :math:`\alpha = (x - \mu)/{\sigma}`.
@@ -1535,10 +1535,10 @@ class RectangleModel(Model):
         :nowrap:
 
         \begin{eqnarray*}
-        &f(x; A, \mu, \sigma, {\mathrm{form={}'linear{}'}})   &= A \{ \min{[1, \max{(0, \alpha_1)}]} + \min{[-1, \max{(0, \alpha_2)}]} \} \\
+        &f(x; A, \mu, \sigma, {\mathrm{form={}'linear{}'}})   &= A \{ \min{[1, \max{(-1, \alpha_1)}]} + \min{[1, \max{(-1, \alpha_2)}]} \}/2 \\
         &f(x; A, \mu, \sigma, {\mathrm{form={}'arctan{}'}})   &= A [\arctan{(\alpha_1)} + \arctan{(\alpha_2)}]/{\pi} \\
-        &f(x; A, \mu, \sigma, {\mathrm{form={}'erf{}'}})      &= A [{\operatorname{erf}}(\alpha_1) + {\operatorname{erf}}(\alpha_2)]/2 \\
-        &f(x; A, \mu, \sigma, {\mathrm{form={}'logistic{}'}}) &= A [1 - \frac{1}{1 + e^{\alpha_1}} - \frac{1}{1 + e^{\alpha_2}} ]
+        &f(x; A, \mu, \sigma, {\mathrm{form={}'erf{}'}})      &= A \left[{\operatorname{erf}}(\alpha_1) + {\operatorname{erf}}(\alpha_2)\right]/2 \\
+        &f(x; A, \mu, \sigma, {\mathrm{form={}'logistic{}'}}) &= A \left[1 - \frac{1}{1 + e^{\alpha_1}} - \frac{1}{1 + e^{\alpha_2}} \right]
         \end{eqnarray*}
 
 


Test patch:
diff --git a/tests/test_NIST_Strd.py b/tests/test_NIST_Strd.py
index f377672c..3419de91 100644
--- a/tests/test_NIST_Strd.py
+++ b/tests/test_NIST_Strd.py
@@ -118,7 +118,7 @@ options:
 --------
   -m  name of fitting method.  One of:
           leastsq, nelder, powell, lbfgsb, bfgs,
-          tnc, cobyla, slsqp, cg, newto-cg
+          tnc, cobyla, slsqp, cg, newton-cg
       leastsq (Levenberg-Marquardt) is the default
 """
     return usage
@@ -183,115 +183,114 @@ def RunNIST_Model(model):
     out1 = NIST_Dataset(model, start='start1', plot=False, verbose=False)
     out2 = NIST_Dataset(model, start='start2', plot=False, verbose=False)
     assert(out1 or out2)
-    return out1 or out2
 
 
 def test_Bennett5():
-    return RunNIST_Model('Bennett5')
+    RunNIST_Model('Bennett5')
 
 
 def test_BoxBOD():
-    return RunNIST_Model('BoxBOD')
+    RunNIST_Model('BoxBOD')
 
 
 def test_Chwirut1():
-    return RunNIST_Model('Chwirut1')
+    RunNIST_Model('Chwirut1')
 
 
 def test_Chwirut2():
-    return RunNIST_Model('Chwirut2')
+    RunNIST_Model('Chwirut2')
 
 
 def test_DanWood():
-    return RunNIST_Model('DanWood')
+    RunNIST_Model('DanWood')
 
 
 def test_ENSO():
-    return RunNIST_Model('ENSO')
+    RunNIST_Model('ENSO')
 
 
 def test_Eckerle4():
-    return RunNIST_Model('Eckerle4')
+    RunNIST_Model('Eckerle4')
 
 
 def test_Gauss1():
-    return RunNIST_Model('Gauss1')
+    RunNIST_Model('Gauss1')
 
 
 def test_Gauss2():
-    return RunNIST_Model('Gauss2')
+    RunNIST_Model('Gauss2')
 
 
 def test_Gauss3():
-    return RunNIST_Model('Gauss3')
+    RunNIST_Model('Gauss3')
 
 
 def test_Hahn1():
-    return RunNIST_Model('Hahn1')
+    RunNIST_Model('Hahn1')
 
 
 def test_Kirby2():
-    return RunNIST_Model('Kirby2')
+    RunNIST_Model('Kirby2')
 
 
 def test_Lanczos1():
-    return RunNIST_Model('Lanczos1')
+    RunNIST_Model('Lanczos1')
 
 
 def test_Lanczos2():
-    return RunNIST_Model('Lanczos2')
+    RunNIST_Model('Lanczos2')
 
 
 def test_Lanczos3():
-    return RunNIST_Model('Lanczos3')
+    RunNIST_Model('Lanczos3')
 
 
 def test_MGH09():
-    return RunNIST_Model('MGH09')
+    RunNIST_Model('MGH09')
 
 
 def test_MGH10():
-    return RunNIST_Model('MGH10')
+    RunNIST_Model('MGH10')
 
 
 def test_MGH17():
-    return RunNIST_Model('MGH17')
+    RunNIST_Model('MGH17')
 
 
 def test_Misra1a():
-    return RunNIST_Model('Misra1a')
+    RunNIST_Model('Misra1a')
 
 
 def test_Misra1b():
-    return RunNIST_Model('Misra1b')
+    RunNIST_Model('Misra1b')
 
 
 def test_Misra1c():
-    return RunNIST_Model('Misra1c')
+    RunNIST_Model('Misra1c')
 
 
 def test_Misra1d():
-    return RunNIST_Model('Misra1d')
+    RunNIST_Model('Misra1d')
 
 
 def test_Nelson():
-    return RunNIST_Model('Nelson')
+    RunNIST_Model('Nelson')
 
 
 def test_Rat42():
-    return RunNIST_Model('Rat42')
+    RunNIST_Model('Rat42')
 
 
 def test_Rat43():
-    return RunNIST_Model('Rat43')
+    RunNIST_Model('Rat43')
 
 
 def test_Roszman1():
-    return RunNIST_Model('Roszman1')
+    RunNIST_Model('Roszman1')
 
 
 def test_Thurber():
-    return RunNIST_Model('Thurber')
+    RunNIST_Model('Thurber')
 
 
 if __name__ == '__main__':
diff --git a/tests/test_lineshapes.py b/tests/test_lineshapes.py
index 8e5a7bee..1d28aad8 100644
--- a/tests/test_lineshapes.py
+++ b/tests/test_lineshapes.py
@@ -73,13 +73,8 @@ def test_x_float_value(lineshape):
                 if par_name != 'x']:
         fnc_args.append(sig.parameters[par].default)
 
-    if lineshape in ('step', 'rectangle'):
-        msg = r"'float' object does not support item assignment"
-        with pytest.raises(TypeError, match=msg):
-            fnc_output = func(*fnc_args)
-    else:
-        fnc_output = func(*fnc_args)
-        assert isinstance(fnc_output, float)
+    fnc_output = func(*fnc_args)
+    assert isinstance(fnc_output, float)
 
 
 rising_form = ['erf', 'logistic', 'atan', 'arctan', 'linear', 'unknown']
@@ -111,6 +106,18 @@ def test_form_argument_step_rectangle(form, lineshape):
         assert len(fnc_output) == len(xvals)
 
 
+@pytest.mark.parametrize('form', rising_form)
+@pytest.mark.parametrize('lineshape', ['step', 'rectangle'])
+def test_value_step_rectangle(form, lineshape):
+    """Test values at mu1/mu2 for step- and rectangle-functions."""
+    func = getattr(lmfit.lineshapes, lineshape)
+    # at position mu1 we should be at A/2
+    assert_almost_equal(func(0), 0.5)
+    # for a rectangular shape we have the same at mu2
+    if lineshape == 'rectangle':
+        assert_almost_equal(func(1), 0.5)
+
+
 thermal_form = ['bose', 'maxwell', 'fermi', 'Bose-Einstein', 'unknown']