Repository: scipy/scipy
Pull request: BUG: fix `nan` output of `special.betaincinv`

#### Reference issue
Closes gh-21426

#### What does this implement/fix?
The underlying issue was fixed in Boost, so I simply updated it.

#### Additional information
@steppi: I do not want to open the can of worms to unify the `stats` and `special` wrappers of boost functions here. This PR is a simple bugfix that can be backported.

Problem statement:
BUG: `special.betaincinv` returns nan
### Describe your issue.

`scipy.special.betaincinv` occasionally returns NaN values from SciPy version 1.14.0.

### Reproducing Code Example

Reproducer:

```python
import numpy as np
import scipy

a = np.array([[5]], dtype='f')
x = np.array([[0.5]], dtype='f')
print(scipy.special.betaincinv(a, a, x))  # expected: 0.5
```


### Error message

Output:
```shell
[[nan]]
```


### SciPy/NumPy/Python version and system information

```shell
scipy: 1.14.1
numpy: 2.1.0
```

Hints from issue discussion:
This is the same underlying problem as https://github.com/scipy/scipy/issues/21303 and will require adjusting the Boost policies of `ibeta_inv_wrap` as was done for that issue. We had only adjusted the policies for `beta_ppf_wrap` which itself wraps `ibeta_inv_wrap`. Does anyone know why we need separate implementations for `special` and `stats` in the first place? I guess due to requiring different policies for some reason?
cc @WarrenWeckesser @dschmitz89 @czgdp1807 
The underlying issue was fixed in boost, let's see if we can remove our workarounds.

Commit message: TST: loosen tolerance for 32bit and single precision

Patch to review:
diff --git a/scipy/_lib/boost_math b/scipy/_lib/boost_math
index 54e5acf7faea..260f7600e95d 160000
--- a/scipy/_lib/boost_math
+++ b/scipy/_lib/boost_math
@@ -1 +1 @@
-Subproject commit 54e5acf7faeab1400031d84bbfb2c46e81d82442
+Subproject commit 260f7600e95dcf87b228d46a4a37cf594df5437b
diff --git a/scipy/special/tests/test_basic.py b/scipy/special/tests/test_basic.py
index efb34b2c5238..7055da7fc147 100644
--- a/scipy/special/tests/test_basic.py
+++ b/scipy/special/tests/test_basic.py
@@ -1445,6 +1445,21 @@ def test_betainc_domain_errors(self, func, args):
             with pytest.raises(special.SpecialFunctionError, match='domain'):
                 special.betainc(*args)
 
+    @pytest.mark.parametrize('dtype', [np.float32, np.float64])
+    def test_gh21426(self, dtype):
+        # Test for gh-21426: betaincinv must not return NaN
+        a = np.array([5.], dtype=dtype)
+        x = np.array([0.5], dtype=dtype)
+        result = special.betaincinv(a, a, x)
+        if sys.maxsize <= 2**32 and dtype == np.float32:
+            # result is not very precise for 32bit architecture and single
+            # precision
+            rtol = 1e-6
+        else:
+
+            rtol = 1e-15
+        assert_allclose(result, x, rtol=rtol)
+
 
 class TestCombinatorics:
     def test_comb(self):


Code review comments:
diff_hunk: @@ -1445,6 +1445,21 @@ def test_betainc_domain_errors(self, func, args):
             with pytest.raises(special.SpecialFunctionError, match='domain'):
                 special.betainc(*args)
 
+    @pytest.mark.parametrize('dtype', [np.float32, np.float64])
+    def test_gh21426(self, dtype):
+        # Test for gh-21426: betaincinv must not return NaN
+        a = np.array([5.], dtype=dtype)
+        x = np.array([0.5], dtype=dtype)
+        result = special.betaincinv(a, a, x)
+        if sys.maxsize <= 2**32 and dtype == np.float32:
+            # result is not very precise for 32bit architecture and single
+            # precision
+            rtol = 1e-6
+        else:
+
path: scipy/special/tests/test_basic.py
text: @author:
```suggestion
```