@@ -347,3 +347,161 @@ def _randint_arg_check(low, high, endpoint, lower_bound, upper_bound):
347347 raise ValueError ("low is greater than high in given interval" )
348348 rng = (high - low )
349349 return low , rng
350+
351+
352+ @register_jitable
353+ def _shuffle_int (bitgen , n , first , data ):
354+ for i in range (n , first , - 1 ):
355+ j = random_bounded_uint64_fill (bitgen , 0 , i , None , 1 , np .uint64 )[0 ]
356+ data [i ], data [j ] = data [j ], data [i ]
357+ return data
358+
359+
360+ @register_jitable
361+ def _choice (bitgen_inst , a , size , replace , p , axis , shuffle ):
362+ pop_size = a .shape [axis ]
363+
364+ if p is not None :
365+ atol = np .sqrt (np .finfo (np .float64 ).eps )
366+ if isinstance (p , np .ndarray ):
367+ if np .issubdtype (p .dtype , np .floating ):
368+ atol = max (atol , np .sqrt (np .finfo (p .dtype ).eps ))
369+
370+ flat_size = np .prod (np .array (size ))
371+
372+ # Actual sampling
373+ if replace :
374+ if p is not None :
375+ cdf = p .cumsum ()
376+ cdf /= cdf [- 1 ]
377+ uniform_samples = bitgen_inst .random (size )
378+ idx = cdf .searchsorted (uniform_samples , side = 'right' )
379+ # searchsorted returns a scalar
380+ idx = np .array (idx , copy = False , dtype = np .int64 )
381+ else :
382+ idx = bitgen_inst .integers (0 , pop_size , size = size , dtype = np .int64 )
383+ else :
384+ if flat_size > pop_size :
385+ raise ValueError ("Cannot take a larger sample than "
386+ "population when replace is False" )
387+ elif flat_size < 0 :
388+ raise ValueError ("negative dimensions are not allowed" )
389+
390+ if p is not None :
391+ if np .count_nonzero (p > 0 ) < flat_size :
392+ raise ValueError ("Fewer non-zero entries in p than size" )
393+ n_uniq = 0
394+ p = p .copy ()
395+ found = np .zeros (size , dtype = np .int64 )
396+ flat_found = found .ravel ()
397+ while n_uniq < flat_size :
398+ x = bitgen_inst .random ((flat_size - n_uniq ,))
399+ if n_uniq > 0 :
400+ p [flat_found [0 :n_uniq ]] = 0
401+ cdf = np .cumsum (p )
402+ cdf /= cdf [- 1 ]
403+ new = cdf .searchsorted (x , side = 'right' )
404+ _ , unique_indices = np .unique (new , return_index = True )
405+ unique_indices .sort ()
406+ new = new .take (unique_indices )
407+ flat_found [n_uniq :n_uniq + new .size ] = new
408+ n_uniq += new .size
409+ idx = found
410+ else :
411+ size_i = flat_size
412+ pop_size_i = pop_size
413+ # This is a heuristic tuning. should be improvable
414+ if shuffle :
415+ cutoff = 50
416+ else :
417+ cutoff = 20
418+ if pop_size_i > 10000 and (size_i > (pop_size_i // cutoff )):
419+ # Tail shuffle size elements
420+ idx = np .arange (0 , pop_size_i )
421+ idx = _shuffle_int (bitgen_inst .bit_generator , pop_size_i ,
422+ max (pop_size_i - size_i , 1 ), idx )
423+ # Copy to allow potentially large array backing idx to be gc
424+ idx = idx [(pop_size - flat_size ):].copy ()
425+ else :
426+ # Floyd's algorithm
427+ idx = np .empty (int (flat_size ), dtype = np .int64 )
428+ # smallest power of 2 larger than 1.2 * flat_size
429+ set_size = (1.2 * size_i )
430+ mask = gen_mask (int (set_size ))
431+ set_size = 1 + mask
432+ hash_set = np .full (set_size , UINT64_MAX - 1 , np .uint64 )
433+ for j in range (pop_size_i - size_i , pop_size_i ):
434+ val = random_bounded_uint64_fill (bitgen_inst .bit_generator ,
435+ 0 , j , None , 1 , np .int64 )[0 ]
436+ loc = val & mask
437+ while hash_set [loc ] != UINT64_MAX - 1 \
438+ and hash_set [loc ] != val :
439+ loc = (loc + 1 ) & mask
440+ if hash_set [loc ] == UINT64_MAX - 1 :
441+ hash_set [loc ] = val
442+ idx [int (j - pop_size_i + size_i )] = val
443+ else : # we need to insert j instead
444+ loc = j & mask
445+ while hash_set [loc ] != UINT64_MAX - 1 :
446+ loc = (loc + 1 ) & mask
447+ hash_set [loc ] = j
448+ idx [int (j - pop_size_i + size_i )] = j
449+ if shuffle :
450+ idx = _shuffle_int (bitgen_inst .bit_generator ,
451+ size_i , 1 , idx )
452+
453+ return idx
454+
455+
456+ def random_interval (bitgen , _max ):
457+ if (_max == 0 ):
458+ return 0
459+
460+ mask = _max
461+
462+ # /* Smallest bit mask >= max */
463+ mask |= mask >> 1
464+ mask |= mask >> 2
465+ mask |= mask >> 4
466+ mask |= mask >> 8
467+ mask |= mask >> 16
468+ mask |= mask >> 32
469+
470+ # /* Search a random value in [0..mask] <= max */
471+ if (_max <= 0xffffffff ):
472+ value = next_uint32 (bitgen )
473+ while (value & mask ) > _max :
474+ value = next_uint32 (bitgen )
475+ else :
476+ value = next_uint64 (bitgen )
477+ while (value & mask ) > _max :
478+ value = next_uint64 (bitgen )
479+
480+ return value
481+
482+
483+ def _shuffle (bitgen , x , axis = 0 ):
484+
485+ if isinstance (x , np .ndarray ):
486+ if x .size == 0 :
487+ return x
488+
489+ x = np .swapaxes (x , 0 , axis )
490+ buf = np .empty_like (x [0 , ...])
491+ for i in range (len (x ) - 1 , 0 , - 1 ):
492+ j = random_interval (bitgen , i )
493+ if i == j :
494+ continue
495+ buf [...] = x [j ]
496+ x [j ] = x [i ]
497+ x [i ] = buf
498+ else :
499+ if axis != 0 :
500+ raise NotImplementedError ("Axis argument is only supported "
501+ "on ndarray objects" )
502+
503+ for i in range (x .size - 1 , 0 , - 1 ):
504+ j = random_interval (bitgen , i )
505+ x [i ], x [j ] = x [j ], x [i ]
506+
507+ return x
0 commit comments