Raised by @oyamad in review of #864 (comment): quantecon.random.draw should be able to accept a random number generator, which recent Numba versions make possible.
Current behaviour
draw is the only function in quantecon.random with no random_state argument. It calls np.random.random() directly, in both the pure-Python body and the @overload implementation, so the generator it uses depends on where it is called from:
| Call site |
Generator used |
How to seed it |
| Python |
NumPy's legacy global state |
np.random.seed(...) at Python level |
| Inside a jitted function |
Numba's own internal state |
np.random.seed(...) inside the jitted function |
These are two different generators. A np.random.seed call at Python level has no effect on the jitted path — a sharp edge for anyone calling draw from inside @njit code and expecting reproducibility. Both are Mersenne Twister and produce the same stream for a given seed, but they advance independently.
This leaves draw inconsistent with probvec and sample_without_replacement, which both accept random_state and work with a np.random.Generator.
Feasibility
Verified on numba 0.62.1 / numpy 2.3.5 / python 3.13.9 — Numba accepts a np.random.Generator in nopython mode and reproduces the host stream exactly:
from numba import njit
import numpy as np
@njit
def h(rng, n):
return rng.random(n)
rng = np.random.default_rng(1234)
assert np.array_equal(h(rng, 5), np.random.default_rng(1234).random(5)) # passes
So the blocker that originally justified draw's signature is gone.
Scope
This is a signature and behaviour change, not a documentation fix, which is why it was split out of #864 rather than carried in it. Points worth settling before implementing:
- Whether the new parameter matches the module's existing
random_state name or moves to a rng name, and whether that decision should apply module-wide rather than to draw alone.
- How the pure-Python and
@overload paths stay in agreement — the overload needs its own handling for a passed generator, and the two implementations should not diverge in output for the same generator and seed.
- Whether
check_random_state (used by the other two functions) can be reached from nopython mode, or whether the jitted path needs a narrower contract that accepts only a Generator.
- Backward compatibility for existing callers that rely on the current global-state behaviour, including jitted callers seeding via
np.random.seed inside jitted code.
Related
The docstring in quantecon/random/utilities.py now documents the two-generator behaviour above, added in 08682a7 on #864. That Notes block should be replaced with the new parameter once this lands.
Raised by @oyamad in review of #864 (comment):
quantecon.random.drawshould be able to accept a random number generator, which recent Numba versions make possible.Current behaviour
drawis the only function inquantecon.randomwith norandom_stateargument. It callsnp.random.random()directly, in both the pure-Python body and the@overloadimplementation, so the generator it uses depends on where it is called from:np.random.seed(...)at Python levelnp.random.seed(...)inside the jitted functionThese are two different generators. A
np.random.seedcall at Python level has no effect on the jitted path — a sharp edge for anyone callingdrawfrom inside@njitcode and expecting reproducibility. Both are Mersenne Twister and produce the same stream for a given seed, but they advance independently.This leaves
drawinconsistent withprobvecandsample_without_replacement, which both acceptrandom_stateand work with anp.random.Generator.Feasibility
Verified on numba 0.62.1 / numpy 2.3.5 / python 3.13.9 — Numba accepts a
np.random.Generatorin nopython mode and reproduces the host stream exactly:So the blocker that originally justified
draw's signature is gone.Scope
This is a signature and behaviour change, not a documentation fix, which is why it was split out of #864 rather than carried in it. Points worth settling before implementing:
random_statename or moves to arngname, and whether that decision should apply module-wide rather than todrawalone.@overloadpaths stay in agreement — the overload needs its own handling for a passed generator, and the two implementations should not diverge in output for the same generator and seed.check_random_state(used by the other two functions) can be reached from nopython mode, or whether the jitted path needs a narrower contract that accepts only aGenerator.np.random.seedinside jitted code.Related
The docstring in
quantecon/random/utilities.pynow documents the two-generator behaviour above, added in 08682a7 on #864. That Notes block should be replaced with the new parameter once this lands.