codecov[bot]: # [Codecov](https://codecov.io/gh/lmfit/lmfit-py/pull/598?src=pr&el=h1) Report
> Merging [#598](https://codecov.io/gh/lmfit/lmfit-py/pull/598?src=pr&el=desc) into [master](https://codecov.io/gh/lmfit/lmfit-py/commit/449c6ed9f2d70c853507933c0e5f39c2ff75635e?src=pr&el=desc) will **decrease** coverage by `0.33%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/lmfit/lmfit-py/pull/598/graphs/tree.svg?width=650&token=hvITDAARAt&height=150&src=pr)](https://codecov.io/gh/lmfit/lmfit-py/pull/598?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #598      +/-   ##
==========================================
- Coverage   90.56%   90.23%   -0.34%     
==========================================
  Files          11       11              
  Lines        3276     3277       +1     
==========================================
- Hits         2967     2957      -10     
- Misses        309      320      +11
```


| [Impacted Files](https://codecov.io/gh/lmfit/lmfit-py/pull/598?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [lmfit/confidence.py](https://codecov.io/gh/lmfit/lmfit-py/pull/598/diff?src=pr&el=tree#diff-bG1maXQvY29uZmlkZW5jZS5weQ==) | `94% <100%> (+0.47%)` | :arrow_up: |
| [lmfit/jsonutils.py](https://codecov.io/gh/lmfit/lmfit-py/pull/598/diff?src=pr&el=tree#diff-bG1maXQvanNvbnV0aWxzLnB5) | `94.11% <0%> (-3.37%)` | :arrow_down: |
| [lmfit/model.py](https://codecov.io/gh/lmfit/lmfit-py/pull/598/diff?src=pr&el=tree#diff-bG1maXQvbW9kZWwucHk=) | `83.42% <0%> (-1.17%)` | :arrow_down: |
| [lmfit/parameter.py](https://codecov.io/gh/lmfit/lmfit-py/pull/598/diff?src=pr&el=tree#diff-bG1maXQvcGFyYW1ldGVyLnB5) | `85.6% <0%> (-0.15%)` | :arrow_down: |
| [lmfit/minimizer.py](https://codecov.io/gh/lmfit/lmfit-py/pull/598/diff?src=pr&el=tree#diff-bG1maXQvbWluaW1pemVyLnB5) | `91.73% <0%> (-0.03%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/lmfit/lmfit-py/pull/598?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/lmfit/lmfit-py/pull/598?src=pr&el=footer). Last update [449c6ed...db80dc5](https://codecov.io/gh/lmfit/lmfit-py/pull/598?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

diff --git a/doc/confidence.rst b/doc/confidence.rst
index 7fad4859..c9d2323f 100644
--- a/doc/confidence.rst
+++ b/doc/confidence.rst
@@ -88,6 +88,23 @@ around the best fit value.  For this problem, it is not necessary to
 calculate confidence intervals, and the estimates of the uncertainties from
 the covariance matrix are sufficient.
 
+Working without standard error estimates
+----------------------------------------
+
+Sometimes the estimation of the standard errors from the covariance 
+matrix fails, especially if values are near given bounds. Hence, to
+find the confidence intervals in these cases, it is necessary to set 
+the errors by hand. Note that the standard error is only used to find an
+upper limit for each value, hence the exact value is not important.
+
+To set the step-size to 10% of the initial value we loop through all
+parameters and set it manually:
+
+.. code-block:: python
+    for p in result.params:
+        result.params[p].stderr = abs(result.params[p].value * 0.1)
+
+
 An advanced example
 -------------------
 
diff --git a/lmfit/confidence.py b/lmfit/confidence.py
index 61ddc78e..ef9dd0ab 100644
--- a/lmfit/confidence.py
+++ b/lmfit/confidence.py
@@ -182,15 +182,11 @@ class ConfidenceInterval(object):
 
         # check that there are at least 2 true variables!
         # check that all stderrs are sensible (including not None or NaN)
-        nvars = 0
+
         for par in self.fit_params:
-            if par.vary:
-                nvars += 1
-            try:
-                if not (par.vary and par.stderr > 0.0):
-                    raise MinimizerException(CONF_ERR_STDERR)
-            except TypeError:
+            if par.vary and (par.stderr is None or par.stderr is np.nan):
                 raise MinimizerException(CONF_ERR_STDERR)
+        nvars = len([p for p in self.params.values() if p.vary])
         if nvars < 2:
             raise MinimizerException(CONF_ERR_NVARS)
 
@@ -255,7 +251,6 @@ class ConfidenceInterval(object):
             try:
                 val = brentq(calc_prob, a_limit,
                              limit, rtol=.5e-4, args=prob)
-
             except ValueError:
                 self.reset_vals()
                 try:
@@ -293,16 +288,35 @@ class ConfidenceInterval(object):
         old_prob = 0
         limit = start_val
         i = 0
+        bound_reached = False
+        max_prob = max(self.probs)
 
-        while old_prob < max(self.probs):
+        while old_prob < max_prob:
             i = i + 1
             limit += step * direction
+            if limit > para.max:
+                limit = para.max
+                bound_reached = True
+            elif limit < para.min:
+                limit = para.min
+                bound_reached = True
 
             new_prob = self.calc_prob(para, limit)
-            rel_change = (new_prob - old_prob) / max(new_prob, old_prob, 1.e-12)
+            rel_change = (new_prob - old_prob) / max(new_prob, old_prob, 1e-12)
             old_prob = new_prob
+            if self.verbose:
+                msg = "P({}={}) = {}, max. prob={}"
+                print(msg.format(para.name, limit, new_prob, max_prob))
 
             # check for convergence
+            if bound_reached:
+                if new_prob < max(self.probs):
+                    errmsg = ("Bound reached with "
+                              "prob({}={}) = {} < max(sigmas)"
+                              ).format(para.name, limit, new_prob)
+                    warn(errmsg)
+                    break
+
             if i > self.maxiter:
                 errmsg = "maxiter={} reached ".format(self.maxiter)
                 errmsg += ("and prob({}={}) = {} < "

BUG: lmfit.conf_interval does not work with len(p_names) = 1
#### Description
The lmfit confidence calculation fails when len(p_names) < 2. This is due to bug in the counting of the free variables: Instead counting all parameters where vary is True, it counts only the one which are in p_names.

diff --git a/tests/test_confidence.py b/tests/test_confidence.py
index f090e24b..99b94fbe 100644
--- a/tests/test_confidence.py
+++ b/tests/test_confidence.py
@@ -12,7 +12,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 +26,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 +54,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 +95,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 +107,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')

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest
apt-get update apt-get install -y gcc
3.7
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
asteval==0.9.31
certifi @ file:///croot/certifi_1671487769961/work/certifi
exceptiongroup==1.2.2
future==1.0.0
importlib-metadata==6.7.0
iniconfig==2.0.0
-e git+https://github.com/lmfit/lmfit-py.git@449c6ed9f2d70c853507933c0e5f39c2ff75635e#egg=lmfit
numpy==1.21.6
packaging==24.0
pluggy==1.2.0
pytest==7.4.4
scipy==1.7.3
six==1.17.0
tomli==2.0.1
typing_extensions==4.7.1
uncertainties==3.1.7
zipp==3.15.0

name: lmfit-py
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=22.3.1=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - asteval==0.9.31
      - exceptiongroup==1.2.2
      - future==1.0.0
      - importlib-metadata==6.7.0
      - iniconfig==2.0.0
      - numpy==1.21.6
      - packaging==24.0
      - pluggy==1.2.0
      - pytest==7.4.4
      - scipy==1.7.3
      - six==1.17.0
      - tomli==2.0.1
      - typing-extensions==4.7.1
      - uncertainties==3.1.7
      - zipp==3.15.0
prefix: /opt/conda/envs/lmfit-py

tests/test_confidence.py::test_confidence_pnames tests/test_confidence.py::test_confidence_bounds_reached
tests/test_confidence.py::test_confidence_leastsq[False] tests/test_confidence.py::test_confidence_leastsq[True] tests/test_confidence.py::test_confidence_sigma_vs_prob tests/test_confidence.py::test_confidence_exceptions tests/test_confidence.py::test_confidence_warnings tests/test_confidence.py::test_confidence_with_trace tests/test_confidence.py::test_confidence_2d tests/test_confidence.py::test_confidence_2d_limits
doc/confidence.rst lmfit/confidence.py
doc/confidence.rst lmfit/confidence.py