From 655bcdc2e98b0fde22565cee18230644e29c9146 Mon Sep 17 00:00:00 2001 From: Matt McKay Date: Wed, 9 Sep 2026 16:26:34 +1000 Subject: [PATCH] FIX: align draw's Python size dispatch with the jitted path `quantecon.random.draw` chose between an array of draws and a single scalar draw with `isinstance(size, int)` in the Python body and `isinstance(size, types.Integer)` in the `@overload`. A NumPy integer is not a Python `int` but is a `types.Integer`, so `draw(cdf, np.int64(10))` returned one scalar from Python and ten draws from a jitted caller, with no error either way. `bool` is the reverse case: it passes `isinstance(x, int)` but Numba types it as `Boolean`. Widening the Python predicate to `(int, np.integer)` and excluding `bool` makes the two paths agree on every input in #918's table, verified including `None` across all four call shapes. The `@overload` needs no change: `types.Boolean` is not a subclass of `types.Integer`, so the jitted path already dispatches every case correctly. Behaviour change: `draw(cdf, np.int64(10))` now returns ten draws from Python where it previously returned one scalar. That is what the caller asked for and what jitted callers already received, but it is silent and needs a line in the 0.12.0 release notes. Closes #918. Replaces #919. Co-Authored-By: Claude Opus 5 (1M context) --- quantecon/random/tests/test_utilities.py | 11 +++++++++++ quantecon/random/utilities.py | 5 ++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/quantecon/random/tests/test_utilities.py b/quantecon/random/tests/test_utilities.py index 30020a907..ca1fc482b 100644 --- a/quantecon/random/tests/test_utilities.py +++ b/quantecon/random/tests/test_utilities.py @@ -125,6 +125,17 @@ def test_return_types(self): out = func(self.cdf, size) assert_(out.shape == (size,)) + def test_numpy_integer_size(self): + """ + A numpy integer `size` must request an array, as a Python `int` + does and as the jitted path already did. See #918. + + """ + size = np.int64(10) + for func in self.draw_funcs: + out = func(self.cdf, size) + assert_(out.shape == (size,)) + def test_return_values(self): for func in self.draw_funcs: out = func(self.cdf) diff --git a/quantecon/random/utilities.py b/quantecon/random/utilities.py index 9ca73ecef..d3982f30f 100644 --- a/quantecon/random/utilities.py +++ b/quantecon/random/utilities.py @@ -246,7 +246,10 @@ def draw(cdf, size=None, rng=None): """ if rng is None: rng = np.random - if isinstance(size, int): + # `bool` subclasses `int` in Python but Numba types it as `Boolean`, + # not `Integer`, so the exclusion keeps this branch in step with the + # `@overload` implementation below. See #918. + if isinstance(size, (int, np.integer)) and not isinstance(size, bool): rs = rng.random(size) out = np.searchsorted(cdf, rs, side='right') return out