Repository: scipy/scipy
File: scipy/special/tests/test_basic.py (Python)

Code before:
        np_n = np.int64(n)
        np_k = np.int64(k)
        res_np = special.comb(np_n, np_k, exact=True)
        res_py = special.comb(n, k, exact=True)
        assert res_np == res_py

    def test_comb_zeros(self):
        assert_equal(special.comb(2, 3, exact=True), 0)
        assert_equal(special.comb(-1, 3, exact=True), 0)
        assert_equal(special.comb(2, -1, exact=True), 0)
        assert_equal(special.comb(2, -1, exact=False), 0)
        assert_allclose(special.comb([2, -1, 2, 10], [3, 3, -1, 3]), [0., 0., 0., 120.])

    def test_comb_exact_non_int_error(self):
        msg = "`exact=True`"
        with pytest.raises(ValueError, match=msg):
            special.comb(3.4, 4, exact=True)
        with pytest.raises(ValueError, match=msg):
            special.comb(3, 4.4, exact=True)

    @pytest.mark.parametrize('N', [0, 5, 10])
    @pytest.mark.parametrize('exact', [True, False])
    def test_comb_repetition_k_zero(self, N, exact):
        # Regression test for gh-23867
        # C(n, 0) should always be 1 for n >= 0, regardless of repetition
        expected = 1 if exact else 1.0
        assert_equal(special.comb(N, 0, exact=exact, repetition=True), expected)

    def test_comb_repetition_k_zero_array(self):
        # Test array-like input with exact=False for gh-23867
        N = np.array([0, 5, 10])
        result = special.comb(N, 0, exact=False, repetition=True)
        expected = np.array([1.0, 1.0, 1.0])
        assert_equal(result, expected)

    def test_perm(self):
        assert_allclose(special.perm([10, 10], [3, 4]), [720., 5040.])
        assert_allclose(special.perm(10, 3), 720., atol=1.5e-7, rtol=0)
        assert_equal(special.perm(10, 3, exact=True), 720)

    def test_perm_zeros(self):
        assert_equal(special.perm(2, 3, exact=True), 0)
        assert_equal(special.perm(-1, 3, exact=True), 0)
        assert_equal(special.perm(2, -1, exact=True), 0)
        assert_equal(special.perm(2, -1, exact=False), 0)
        assert_allclose(special.perm([2, -1, 2, 10], [3, 3, -1, 3]), [0., 0., 0., 720.])

    def test_perm_iv(self):
        # currently `exact=True` only support scalars
        with pytest.raises(ValueError, match="scalar integers"):
            special.perm([1, 2], [4, 5], exact=True)

Review comment (suggestion):
This caught my eye while reading through the diff--I was skeptical that the current assertion would distinguish between `1` and `1.0` and indeed the tests still pass with that removed:

```diff
--- a/scipy/special/tests/test_basic.py
+++ b/scipy/special/tests/test_basic.py
@@ -1566,8 +1566,7 @@ class TestCombinatorics:
     def test_comb_repetition_k_zero(self, N, exact):
         # Regression test for gh-23867
         # C(n, 0) should always be 1 for n >= 0, regardless of repetition
-        expected = 1 if exact else 1.0
-        assert_equal(special.comb(N, 0, exact=exact, repetition=True), expected)
+        assert_equal(special.comb(N, 0, exact=exact, repetition=True), 1)
```

I suppose the distinction is relevant to some degree, given the docstring and what Joren does with the `comb` overloads at `scipy-stubs/special/_basic.pyi`. I don't know that we actually need to worry about that particular detail in this set of tests, but if we do I suppose an `isinstance` check might suit for distinguishing between the exact and not-exact return types/algorithms.

Maybe wait for Joren's input on that since I'm just doing a quick drive by review.

Code after:
        np_n = np.int64(n)
        np_k = np.int64(k)
        res_np = special.comb(np_n, np_k, exact=True)
        res_py = special.comb(n, k, exact=True)
        assert res_np == res_py

    def test_comb_zeros(self):
        assert_equal(special.comb(2, 3, exact=True), 0)
        assert_equal(special.comb(-1, 3, exact=True), 0)
        assert_equal(special.comb(2, -1, exact=True), 0)
        assert_equal(special.comb(2, -1, exact=False), 0)
        assert_allclose(special.comb([2, -1, 2, 10], [3, 3, -1, 3]), [0., 0., 0., 120.])

    def test_comb_exact_non_int_error(self):
        msg = "`exact=True`"
        with pytest.raises(ValueError, match=msg):
            special.comb(3.4, 4, exact=True)
        with pytest.raises(ValueError, match=msg):
            special.comb(3, 4.4, exact=True)

    @pytest.mark.parametrize('N', [0, 5, 10])
    @pytest.mark.parametrize('exact', [True, False])
    def test_comb_repetition_k_zero(self, N, exact):
        # Regression test for gh-23867
        # C(n, 0) should always be 1 for n >= 0, regardless of repetition
        actual = special.comb(N, 0, exact=exact, repetition=True)
        assert actual == 1
        assert type(actual) is int if exact else np.float64

    def test_comb_repetition_k_zero_array(self):
        # Test array-like input with exact=False for gh-23867
        N = np.array([0, 5, 10])
        result = special.comb(N, 0, exact=False, repetition=True)
        expected = np.array([1.0, 1.0, 1.0])
        assert_equal(result, expected)

    def test_perm(self):
        assert_allclose(special.perm([10, 10], [3, 4]), [720., 5040.])
        assert_allclose(special.perm(10, 3), 720., atol=1.5e-7, rtol=0)
        assert_equal(special.perm(10, 3, exact=True), 720)

    def test_perm_zeros(self):
        assert_equal(special.perm(2, 3, exact=True), 0)
        assert_equal(special.perm(-1, 3, exact=True), 0)
        assert_equal(special.perm(2, -1, exact=True), 0)
        assert_equal(special.perm(2, -1, exact=False), 0)
        assert_allclose(special.perm([2, -1, 2, 10], [3, 3, -1, 3]), [0., 0., 0., 720.])

    def test_perm_iv(self):
        # currently `exact=True` only support scalars
        with pytest.raises(ValueError, match="scalar integers"):