@@ -392,6 +392,70 @@ cdef extern from "mkl_distributions.h":
392392 cnp.npy_int64 high
393393 ) noexcept nogil
394394
395+ void irk_rand_bool_broadcast(
396+ irk_state * state,
397+ cnp.npy_intp len ,
398+ cnp.npy_bool * res,
399+ const cnp.npy_bool * low,
400+ const cnp.npy_bool * high
401+ ) noexcept nogil
402+ void irk_rand_uint8_broadcast(
403+ irk_state * state,
404+ cnp.npy_intp len ,
405+ cnp.npy_uint8 * res,
406+ const cnp.npy_uint8 * low,
407+ const cnp.npy_uint8 * high
408+ ) noexcept nogil
409+ void irk_rand_int8_broadcast(
410+ irk_state * state,
411+ cnp.npy_intp len ,
412+ cnp.npy_int8 * res,
413+ const cnp.npy_int8 * low,
414+ const cnp.npy_int8 * high
415+ ) noexcept nogil
416+ void irk_rand_uint16_broadcast(
417+ irk_state * state,
418+ cnp.npy_intp len ,
419+ cnp.npy_uint16 * res,
420+ const cnp.npy_uint16 * low,
421+ const cnp.npy_uint16 * high
422+ ) noexcept nogil
423+ void irk_rand_int16_broadcast(
424+ irk_state * state,
425+ cnp.npy_intp len ,
426+ cnp.npy_int16 * res,
427+ const cnp.npy_int16 * low,
428+ const cnp.npy_int16 * high
429+ ) noexcept nogil
430+ void irk_rand_uint32_broadcast(
431+ irk_state * state,
432+ cnp.npy_intp len ,
433+ cnp.npy_uint32 * res,
434+ const cnp.npy_uint32 * low,
435+ const cnp.npy_uint32 * high
436+ ) noexcept nogil
437+ void irk_rand_int32_broadcast(
438+ irk_state * state,
439+ cnp.npy_intp len ,
440+ cnp.npy_int32 * res,
441+ const cnp.npy_int32 * low,
442+ const cnp.npy_int32 * high
443+ ) noexcept nogil
444+ void irk_rand_uint64_broadcast(
445+ irk_state * state,
446+ cnp.npy_intp len ,
447+ cnp.npy_uint64 * res,
448+ const cnp.npy_uint64 * low,
449+ const cnp.npy_uint64 * high
450+ ) noexcept nogil
451+ void irk_rand_int64_broadcast(
452+ irk_state * state,
453+ cnp.npy_intp len ,
454+ cnp.npy_int64 * res,
455+ const cnp.npy_int64 * low,
456+ const cnp.npy_int64 * high
457+ ) noexcept nogil
458+
395459 void irk_long_vec(
396460 irk_state * state, cnp.npy_intp len , long * res
397461 ) noexcept nogil
@@ -1873,22 +1937,26 @@ cdef class _MKLRandomState:
18731937 # a few places. It would be easy to template them.
18741938
18751939 def _choose_randint_type (self , dtype ):
1876- _randint_type = {
1877- " bool" : (0 , 2 , self ._rand_bool ),
1878- " int8" : (- 2 ** 7 , 2 ** 7 , self ._rand_int8 ),
1879- " int16" : (- 2 ** 15 , 2 ** 15 , self ._rand_int16 ),
1880- " int32" : (- 2 ** 31 , 2 ** 31 , self ._rand_int32 ),
1881- " int64" : (- 2 ** 63 , 2 ** 63 , self ._rand_int64 ),
1882- " uint8" : (0 , 2 ** 8 , self ._rand_uint8 ),
1883- " uint16" : (0 , 2 ** 16 , self ._rand_uint16 ),
1884- " uint32" : (0 , 2 ** 32 , self ._rand_uint32 ),
1885- " uint64" : (0 , 2 ** 64 , self ._rand_uint64)
1940+ _randint_bounds = {
1941+ " bool" : (0 , 2 ),
1942+ " int8" : (- 2 ** 7 , 2 ** 7 ),
1943+ " int16" : (- 2 ** 15 , 2 ** 15 ),
1944+ " int32" : (- 2 ** 31 , 2 ** 31 ),
1945+ " int64" : (- 2 ** 63 , 2 ** 63 ),
1946+ " uint8" : (0 , 2 ** 8 ),
1947+ " uint16" : (0 , 2 ** 16 ),
1948+ " uint32" : (0 , 2 ** 32 ),
1949+ " uint64" : (0 , 2 ** 64 ),
18861950 }
18871951
18881952 key = np.dtype(dtype).name
1889- if key not in _randint_type :
1953+ if key not in _randint_bounds :
18901954 raise TypeError (f' Unsupported dtype "{key}" for randint' )
1891- return _randint_type[key]
1955+
1956+ lowbnd, highbnd = _randint_bounds[key]
1957+ return (lowbnd, highbnd,
1958+ getattr (self , f" _rand_{key}" ),
1959+ getattr (self , f" _rand_{key}_broadcast" ))
18921960
18931961 # generates typed random integer in [low, high]
18941962 def _rand_bool (self , cnp.npy_bool low , cnp.npy_bool high , size ):
@@ -2119,6 +2187,150 @@ cdef class _MKLRandomState:
21192187 irk_rand_uint64_vec(self .internal_state, cnt, out, low, high)
21202188 return array
21212189
2190+ # Broadcasted variants of the typed generators for randint
2191+ def _rand_bool_broadcast (self , cnp.ndarray low , cnp.ndarray high ,
2192+ cnp.ndarray out ):
2193+ cdef cnp.npy_intp cnt = cnp.PyArray_SIZE(out)
2194+ cdef cnp.npy_bool * out_p = < cnp.npy_bool * > cnp.PyArray_DATA(out)
2195+ cdef cnp.npy_bool * low_p = < cnp.npy_bool * > cnp.PyArray_DATA(low)
2196+ cdef cnp.npy_bool * high_p = < cnp.npy_bool * > cnp.PyArray_DATA(high)
2197+ with nogil:
2198+ irk_rand_bool_broadcast(
2199+ self .internal_state, cnt, out_p, low_p, high_p
2200+ )
2201+
2202+ def _rand_int8_broadcast (self , cnp.ndarray low , cnp.ndarray high ,
2203+ cnp.ndarray out ):
2204+ cdef cnp.npy_intp cnt = cnp.PyArray_SIZE(out)
2205+ cdef cnp.npy_int8 * out_p = < cnp.npy_int8 * > cnp.PyArray_DATA(out)
2206+ cdef cnp.npy_int8 * low_p = < cnp.npy_int8 * > cnp.PyArray_DATA(low)
2207+ cdef cnp.npy_int8 * high_p = < cnp.npy_int8 * > cnp.PyArray_DATA(high)
2208+ with nogil:
2209+ irk_rand_int8_broadcast(
2210+ self .internal_state, cnt, out_p, low_p, high_p
2211+ )
2212+
2213+ def _rand_int16_broadcast (self , cnp.ndarray low , cnp.ndarray high ,
2214+ cnp.ndarray out ):
2215+ cdef cnp.npy_intp cnt = cnp.PyArray_SIZE(out)
2216+ cdef cnp.npy_int16 * out_p = < cnp.npy_int16 * > cnp.PyArray_DATA(out)
2217+ cdef cnp.npy_int16 * low_p = < cnp.npy_int16 * > cnp.PyArray_DATA(low)
2218+ cdef cnp.npy_int16 * high_p = < cnp.npy_int16 * > cnp.PyArray_DATA(high)
2219+ with nogil:
2220+ irk_rand_int16_broadcast(
2221+ self .internal_state, cnt, out_p, low_p, high_p
2222+ )
2223+
2224+ def _rand_int32_broadcast (self , cnp.ndarray low , cnp.ndarray high ,
2225+ cnp.ndarray out ):
2226+ cdef cnp.npy_intp cnt = cnp.PyArray_SIZE(out)
2227+ cdef cnp.npy_int32 * out_p = < cnp.npy_int32 * > cnp.PyArray_DATA(out)
2228+ cdef cnp.npy_int32 * low_p = < cnp.npy_int32 * > cnp.PyArray_DATA(low)
2229+ cdef cnp.npy_int32 * high_p = < cnp.npy_int32 * > cnp.PyArray_DATA(high)
2230+ with nogil:
2231+ irk_rand_int32_broadcast(
2232+ self .internal_state, cnt, out_p, low_p, high_p
2233+ )
2234+
2235+ def _rand_int64_broadcast (self , cnp.ndarray low , cnp.ndarray high ,
2236+ cnp.ndarray out ):
2237+ cdef cnp.npy_intp cnt = cnp.PyArray_SIZE(out)
2238+ cdef cnp.npy_int64 * out_p = < cnp.npy_int64 * > cnp.PyArray_DATA(out)
2239+ cdef cnp.npy_int64 * low_p = < cnp.npy_int64 * > cnp.PyArray_DATA(low)
2240+ cdef cnp.npy_int64 * high_p = < cnp.npy_int64 * > cnp.PyArray_DATA(high)
2241+ with nogil:
2242+ irk_rand_int64_broadcast(
2243+ self .internal_state, cnt, out_p, low_p, high_p
2244+ )
2245+
2246+ def _rand_uint8_broadcast (self , cnp.ndarray low , cnp.ndarray high ,
2247+ cnp.ndarray out ):
2248+ cdef cnp.npy_intp cnt = cnp.PyArray_SIZE(out)
2249+ cdef cnp.npy_uint8 * out_p = < cnp.npy_uint8 * > cnp.PyArray_DATA(out)
2250+ cdef cnp.npy_uint8 * low_p = < cnp.npy_uint8 * > cnp.PyArray_DATA(low)
2251+ cdef cnp.npy_uint8 * high_p = < cnp.npy_uint8 * > cnp.PyArray_DATA(high)
2252+ with nogil:
2253+ irk_rand_uint8_broadcast(
2254+ self .internal_state, cnt, out_p, low_p, high_p
2255+ )
2256+
2257+ def _rand_uint16_broadcast (self , cnp.ndarray low , cnp.ndarray high ,
2258+ cnp.ndarray out ):
2259+ cdef cnp.npy_intp cnt = cnp.PyArray_SIZE(out)
2260+ cdef cnp.npy_uint16 * out_p = < cnp.npy_uint16 * > cnp.PyArray_DATA(out)
2261+ cdef cnp.npy_uint16 * low_p = < cnp.npy_uint16 * > cnp.PyArray_DATA(low)
2262+ cdef cnp.npy_uint16 * high_p = < cnp.npy_uint16 * > cnp.PyArray_DATA(high)
2263+ with nogil:
2264+ irk_rand_uint16_broadcast(
2265+ self .internal_state, cnt, out_p, low_p, high_p
2266+ )
2267+
2268+ def _rand_uint32_broadcast (self , cnp.ndarray low , cnp.ndarray high ,
2269+ cnp.ndarray out ):
2270+ cdef cnp.npy_intp cnt = cnp.PyArray_SIZE(out)
2271+ cdef cnp.npy_uint32 * out_p = < cnp.npy_uint32 * > cnp.PyArray_DATA(out)
2272+ cdef cnp.npy_uint32 * low_p = < cnp.npy_uint32 * > cnp.PyArray_DATA(low)
2273+ cdef cnp.npy_uint32 * high_p = < cnp.npy_uint32 * > cnp.PyArray_DATA(high)
2274+ with nogil:
2275+ irk_rand_uint32_broadcast(
2276+ self .internal_state, cnt, out_p, low_p, high_p
2277+ )
2278+
2279+ def _rand_uint64_broadcast (self , cnp.ndarray low , cnp.ndarray high ,
2280+ cnp.ndarray out ):
2281+ cdef cnp.npy_intp cnt = cnp.PyArray_SIZE(out)
2282+ cdef cnp.npy_uint64 * out_p = < cnp.npy_uint64 * > cnp.PyArray_DATA(out)
2283+ cdef cnp.npy_uint64 * low_p = < cnp.npy_uint64 * > cnp.PyArray_DATA(low)
2284+ cdef cnp.npy_uint64 * high_p = < cnp.npy_uint64 * > cnp.PyArray_DATA(high)
2285+ with nogil:
2286+ irk_rand_uint64_broadcast(
2287+ self .internal_state, cnt, out_p, low_p, high_p
2288+ )
2289+
2290+ def _randint_broadcast (self , low_arr , high_arr , size , _dtype ,
2291+ lowbnd , highbnd , broadcast_func ):
2292+ # output shape
2293+ # `size` if given, else the broadcast of the bounds
2294+ if size is None :
2295+ out_shape = np.broadcast_shapes(low_arr.shape, high_arr.shape)
2296+ elif isinstance (size, (int , np.integer)):
2297+ out_shape = (int (size),)
2298+ else :
2299+ out_shape = tuple (int (s) for s in size)
2300+
2301+ # size-1 bounds may collapse into a smaller-rank `size`
2302+ bshape = np.broadcast_shapes(low_arr.shape, high_arr.shape, out_shape)
2303+ if np.prod(bshape) != np.prod(out_shape):
2304+ raise ValueError (" shape mismatch: bounds cannot broadcast to size" )
2305+ low_b = np.broadcast_to(low_arr, bshape)
2306+ high_b = np.broadcast_to(high_arr, bshape)
2307+
2308+ if np.prod(out_shape) == 0 :
2309+ return np.empty(out_shape, dtype = _dtype)
2310+
2311+ max_high = int (np.max(high_arr))
2312+ if int (np.min(low_arr)) < lowbnd:
2313+ raise ValueError (f" low is out of bounds for {_dtype.name}" )
2314+ if max_high > highbnd:
2315+ raise ValueError (f" high is out of bounds for {_dtype.name}" )
2316+ if np.any(low_arr >= high_arr):
2317+ raise ValueError (" low >= high" )
2318+
2319+ # inclusive high (high - 1); widen small dtypes to int64 to avoid
2320+ # overflow, but subtract first when high exceeds int64
2321+ if max_high <= 2 ** 63 - 1 :
2322+ high_incl = high_b.astype(np.int64) - 1
2323+ else :
2324+ high_incl = high_b - 1
2325+ low_c = np.ascontiguousarray(low_b, dtype = _dtype)
2326+ high_c = np.ascontiguousarray(high_incl, dtype = _dtype)
2327+ out = np.empty(out_shape, dtype = _dtype)
2328+
2329+ with self .lock:
2330+ broadcast_func(low_c, high_c, out)
2331+
2332+ return out
2333+
21222334 def randint (self , low , high = None , size = None , dtype = int ):
21232335 """
21242336 randint(low, high=None, size=None, dtype=int)
@@ -2131,13 +2343,15 @@ cdef class _MKLRandomState:
21312343
21322344 Parameters
21332345 ----------
2134- low : int
2346+ low : int or array_like of ints
21352347 Lowest (signed) integer to be drawn from the distribution (unless
21362348 ``high=None``, in which case this parameter is the *highest* such
2137- integer).
2138- high : int, optional
2349+ integer). If an array is given, it must broadcast with `high` (and
2350+ with `size`, if provided).
2351+ high : int or array_like of ints, optional
21392352 If provided, one above the largest (signed) integer to be drawn
21402353 from the distribution (see above for behavior if ``high=None``).
2354+ If an array is given, it must broadcast with `low`.
21412355 size : int or tuple of ints, optional
21422356 Output shape. If the given shape is, e.g., ``(m, n, k)``, then
21432357 ``m * n * k`` samples are drawn. Default is None, in which case a
@@ -2166,16 +2380,32 @@ cdef class _MKLRandomState:
21662380 Examples
21672381 --------
21682382 >>> mkl_random.randint(2, size=10)
2169- array([1, 0, 0, 0, 1, 1, 0, 0, 1, 0])
2383+ array([1, 0, 0, 0, 1, 1, 0, 0, 1, 0]) # random
21702384 >>> mkl_random.randint(1, size=10)
21712385 array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
21722386
21732387 Generate a 2 x 4 array of ints between 0 and 4, inclusive:
21742388
21752389 >>> mkl_random.randint(5, size=(2, 4))
2176- array([[4, 0, 2, 1],
2390+ array([[4, 0, 2, 1], # random
21772391 [3, 2, 2, 0]])
21782392
2393+ Generate a 1 x 3 array with 3 different upper bounds
2394+
2395+ >>> mkl_random.randint(1, [3, 5, 10])
2396+ array([2, 4, 7]) # random
2397+
2398+ Generate a 1 by 3 array with 3 different lower bounds
2399+
2400+ >>> mkl_random.randint([1, 5, 7], 10)
2401+ array([6, 6, 9]) # random
2402+
2403+ Generate a 2 by 4 array using broadcasting with dtype of uint8
2404+
2405+ >>> mkl_random.randint([1, 3, 5, 7], [[10], [20]], dtype=np.uint8)
2406+ array([[ 8, 7, 7, 7], # random
2407+ [18, 17, 19, 17]], dtype=uint8)
2408+
21792409 """
21802410 if high is None :
21812411 high = low
@@ -2191,30 +2421,43 @@ cdef class _MKLRandomState:
21912421 " ValueError" , DeprecationWarning )
21922422 _dtype = _dtype.newbyteorder()
21932423
2194- if size is not None :
2195- if (np.prod(size) == 0 ):
2196- return np.empty(size, dtype = np.dtype(_dtype))
2424+ lowbnd, highbnd, randfunc, broadcast_func = \
2425+ self ._choose_randint_type(_dtype)
21972426
2198- lowbnd, highbnd, randfunc = self ._choose_randint_type(_dtype)
2427+ low_arr = np.asarray(low)
2428+ high_arr = np.asarray(high)
21992429
2200- if low < lowbnd:
2201- raise ValueError (
2202- f" low is out of bounds for {np.dtype(_dtype).name}"
2203- )
2204- if high > highbnd:
2205- raise ValueError (
2206- f" high is out of bounds for {np.dtype(_dtype).name}"
2207- )
2208- if low >= high:
2209- raise ValueError (" low >= high" )
2430+ if low_arr.ndim == 0 and high_arr.ndim == 0 :
2431+ # Fast path for scalar
2432+ if size is not None and np.prod(size) == 0 :
2433+ return np.empty(size, dtype = _dtype)
22102434
2211- with self .lock:
2212- ret = randfunc(low, high - 1 , size )
2435+ low = int (low)
2436+ high = int ( high)
22132437
2214- if size is None and dtype in (bool , int ):
2215- return dtype(ret)
2438+ if low < lowbnd:
2439+ raise ValueError (
2440+ f" low is out of bounds for {_dtype.name}"
2441+ )
2442+ if high > highbnd:
2443+ raise ValueError (
2444+ f" high is out of bounds for {_dtype.name}"
2445+ )
2446+ if low >= high:
2447+ raise ValueError (" low >= high" )
22162448
2217- return ret
2449+ with self .lock:
2450+ ret = randfunc(low, high - 1 , size)
2451+
2452+ if size is None and dtype in (bool , int ):
2453+ return dtype(ret)
2454+
2455+ return ret
2456+
2457+ # Broadcast path (at least one of `low`/`high` is array_like)
2458+ return self ._randint_broadcast(
2459+ low_arr, high_arr, size, _dtype, lowbnd, highbnd, broadcast_func
2460+ )
22182461
22192462 def bytes (self , cnp.npy_intp length ):
22202463 """
0 commit comments