Repository: scipy/scipy
File: scipy/optimize/tests/test_zeros.py (Python)

Code before:

    with pytest.raises(ValueError, match="xtol too small"):
       method(f, -1e8, 1e7, args=args, xtol=-1e-6, rtol=TOL)
    with pytest.raises(ValueError, match="rtol too small"):
       method(f, -1e8, 1e7, args=args, xtol=1e-6, rtol=rtolbad)

class TestRidderUnderflow:
    def test_gh_issue_underflow(self):
        # Regression test for underflow in Ridder's method.
        # Previously, intermediate calculations (fm*fm) would underflow 
        # to zero before the ratio converged, causing a Runtime Error.
        
        def f(x): return x**5

        # Before the fix, this raised a RuntimeError.
        root, result = optimize.ridder(
            f, -1, 5, 
            xtol=1e-300, 
            full_output=True, 
            maxiter=10000
        )
        
        assert result.converged
        assert abs(root) < 1e-10  # Ensuring zero is found

    def test_early_exit_funcalls(self):
        # Test case for when midpoint is reached early (fm == 0).
        def f(x): return x

        root, result = optimize.ridder(
            f, -1, 1, 
            full_output=True
        )
        
        assert result.converged
        assert root == 0.0
        assert result.function_calls == 3


Review comment (performance):
Can you change `test_early_exit_funcalls` to check that the number of evaluations is correct? This checks that it reports 3 evaluations, but the function may have called it fewer or more times.

For example, many of our tests use a pattern like this:

```
    def test_early_exit_funcalls(self):
        # Test case for when midpoint is reached early (fm == 0).
        nfev = 0
        def f(x):
            nonlocal nfev
            nfev += 1
            return x

        root, result = optimize.ridder(
            f, -1, 1, 
            full_output=True
        )
        
        assert result.converged
        assert root == 0.0
        assert result.function_calls == nfev
        assert result.function_calls == 3
```

Code after:

    with pytest.raises(ValueError, match="xtol too small"):
       method(f, -1e8, 1e7, args=args, xtol=-1e-6, rtol=TOL)
    with pytest.raises(ValueError, match="rtol too small"):
       method(f, -1e8, 1e7, args=args, xtol=1e-6, rtol=rtolbad)

class TestRidderUnderflow:
    def test_gh_issue_underflow(self):
        # Regression test for underflow in Ridder's method.
        # Previously, intermediate calculations (fm*fm) would underflow 
        # to zero before the ratio converged, causing a Runtime Error.
        
        def f(x): return x**5

        # Before the fix, this raised a RuntimeError.
        root, result = optimize.ridder(
            f, -1, 5, 
            xtol=1e-300, 
            full_output=True, 
            maxiter=10000
        )
        
        assert result.converged
        assert abs(root) < 1e-10  # Ensuring zero is found

    def test_early_exit_funcalls(self):
        # Test case for when midpoint is reached early (fm == 0).
        nfev = 0
        def f(x):
            nonlocal nfev
            nfev += 1
            return x

        # Root == 0, the midpoint of [-1, 1].
        # Calls: f(-1), f(1) [init], then f(0) [iter 1] -> Exit.
        # Total = 3 calls.
        root, result = optimize.ridder(
            f, -1, 1, 
            full_output=True
        )
        
        assert result.converged
        assert root == 0.0
        assert result.function_calls == nfev
        assert result.function_calls == 3