Skip to content

Extend random.draw to accept a random number generator #916

Description

@mmcky

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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions