@@ -12,6 +12,7 @@ _randint_type = {'bool': (0, 2),
12
12
ctypedef np.npy_bool bool_t
13
13
14
14
cdef inline uint64_t _gen_mask(uint64_t max_val) nogil:
15
+ """Mask generator for use in bounded random numbers"""
15
16
# Smallest bit mask >= max
16
17
cdef uint64_t mask = max_val
17
18
mask |= mask >> 1
@@ -21,53 +22,23 @@ cdef inline uint64_t _gen_mask(uint64_t max_val) nogil:
21
22
mask |= mask >> 16
22
23
mask |= mask >> 32
23
24
return mask
25
+
26
+
24
27
{{
25
28
py:
26
- bc_ctypes = (('uint32', 'uint32', 'uint64', 'NPY_UINT64', 0, 0, 0, '0X100000000ULL'),
29
+ type_info = (('uint32', 'uint32', 'uint64', 'NPY_UINT64', 0, 0, 0, '0X100000000ULL'),
27
30
('uint16', 'uint16', 'uint32', 'NPY_UINT32', 1, 16, 0, '0X10000UL'),
28
31
('uint8', 'uint8', 'uint16', 'NPY_UINT16', 3, 8, 0, '0X100UL'),
29
32
('bool','bool', 'uint8', 'NPY_UINT8', 31, 1, 0, '0x2UL'),
30
33
('int32', 'uint32', 'uint64', 'NPY_INT64', 0, 0, '-0x80000000LL', '0x80000000LL'),
31
34
('int16', 'uint16', 'uint32', 'NPY_INT32', 1, 16, '-0x8000LL', '0x8000LL' ),
32
35
('int8', 'uint8', 'uint16', 'NPY_INT16', 3, 8, '-0x80LL', '0x80LL' ),
33
36
)}}
34
- {{for nptype, utype, nptype_up, npctype, remaining, bitshift, lb, ub in bc_ctypes }}
37
+ {{for nptype, utype, nptype_up, npctype, remaining, bitshift, lb, ub in type_info }}
35
38
{{ py: otype = nptype + '_' if nptype == 'bool' else nptype }}
36
- cdef object _rand_{{nptype}}(object low, object high, object size, aug_state *state, object lock):
37
- """
38
- _rand_{{nptype}}(low, high, size, *state, lock)
39
-
40
- Return random np.{{nptype}} integers between `low` and `high`, inclusive.
41
-
42
- Return random integers from the "discrete uniform" distribution in the
43
- closed interval [`low`, `high`). If `high` is None (the default),
44
- then results are from [0, `low`). On entry the arguments are presumed
45
- to have been validated for size and order for the np.{{nptype}} type.
46
39
47
- Parameters
48
- ----------
49
- low : int or array-like
50
- Lowest (signed) integer to be drawn from the distribution (unless
51
- ``high=None``, in which case this parameter is the *highest* such
52
- integer).
53
- high : int or array-like
54
- If provided, the largest (signed) integer to be drawn from the
55
- distribution (see above for behavior if ``high=None``).
56
- size : int or tuple of ints
57
- Output shape. If the given shape is, e.g., ``(m, n, k)``, then
58
- ``m * n * k`` samples are drawn. Default is None, in which case a
59
- single value is returned.
60
- state : augmented random state
61
- State to use in the core random number generators
62
- lock : threading.Lock
63
- Lock to prevent multiple using a single RandomState simultaneously
64
-
65
- Returns
66
- -------
67
- out : python scalar or ndarray of np.{{nptype}}
68
- `size`-shaped array of random integers from the appropriate
69
- distribution, or a single such random int if `size` not provided.
70
- """
40
+ cdef object _rand_{{nptype}}_broadcast(np.ndarray low, np.ndarray high, object size, aug_state *state, object lock):
41
+ """Array path for smaller integer types"""
71
42
cdef {{utype}}_t rng, last_rng, off, val, mask, out_val
72
43
cdef uint32_t buf
73
44
cdef {{utype}}_t *out_data
@@ -77,36 +48,6 @@ cdef object _rand_{{nptype}}(object low, object high, object size, aug_state *st
77
48
cdef np.broadcast it
78
49
cdef int buf_rem = 0
79
50
80
- low = np.array(low, copy=False)
81
- high = np.array(high, copy=False)
82
- low_ndim = np.PyArray_NDIM(<np.ndarray>low)
83
- high_ndim = np.PyArray_NDIM(<np.ndarray>high)
84
- if ((low_ndim == 0 or (low_ndim==1 and low.size==1 and size is not None)) and
85
- (high_ndim == 0 or (high_ndim==1 and high.size==1 and size is not None))):
86
- low = int(low)
87
- high = int(high)
88
-
89
- if low < {{lb}}:
90
- raise ValueError("low is out of bounds for {{nptype}}")
91
- if high > {{ub}}:
92
- raise ValueError("high is out of bounds for {{nptype}}")
93
- if low >= high:
94
- raise ValueError("low >= high")
95
-
96
- high -= 1
97
- rng = <{{utype}}_t>(high - low)
98
- off = <{{utype}}_t>(<{{nptype}}_t>low)
99
- if size is None:
100
- with lock:
101
- random_bounded_{{utype}}_fill(state, off, rng, 1, &out_val)
102
- return np.{{otype}}(<{{nptype}}_t>out_val)
103
- else:
104
- out_arr = <np.ndarray>np.empty(size, np.{{nptype}})
105
- cnt = np.PyArray_SIZE(out_arr)
106
- out_data = <{{utype}}_t *>np.PyArray_DATA(out_arr)
107
- with lock, nogil:
108
- random_bounded_{{utype}}_fill(state, off, rng, cnt, out_data)
109
- return out_arr
110
51
111
52
# Array path
112
53
low_arr = <np.ndarray>low
@@ -129,10 +70,10 @@ cdef object _rand_{{nptype}}(object low, object high, object size, aug_state *st
129
70
130
71
it = np.PyArray_MultiIterNew3(low_arr, high_arr, out_arr)
131
72
out_data = <{{utype}}_t *>np.PyArray_DATA(out_arr)
132
- n = np.PyArray_SIZE(out_arr)
73
+ cnt = np.PyArray_SIZE(out_arr)
133
74
mask = last_rng = 0
134
75
with lock, nogil:
135
- for i in range(n ):
76
+ for i in range(cnt ):
136
77
low_v = (<{{nptype_up}}_t*>np.PyArray_MultiIter_DATA(it, 0))[0]
137
78
high_v = (<{{nptype_up}}_t*>np.PyArray_MultiIter_DATA(it, 1))[0]
138
79
rng = <{{utype}}_t>((high_v - 1) - low_v)
@@ -145,16 +86,91 @@ cdef object _rand_{{nptype}}(object low, object high, object size, aug_state *st
145
86
out_data[i] = random_buffered_bounded_{{utype}}(state, off, rng, mask, &buf_rem, &buf)
146
87
147
88
np.PyArray_MultiIter_NEXT(it)
148
-
149
89
return out_arr
150
90
{{endfor}}
91
+
151
92
{{
152
93
py:
153
- big_bc_ctypes = (('uint64', 'uint64', 'NPY_UINT64', '0x0ULL', '0xFFFFFFFFFFFFFFFFULL'),
94
+ big_type_info = (('uint64', 'uint64', 'NPY_UINT64', '0x0ULL', '0xFFFFFFFFFFFFFFFFULL'),
154
95
('int64', 'uint64', 'NPY_INT64', '-0x8000000000000000LL', '0x7FFFFFFFFFFFFFFFLL' )
155
96
)}}
156
- {{for nptype, utype, npctype, lb, ub in big_bc_ctypes }}
97
+ {{for nptype, utype, npctype, lb, ub in big_type_info }}
157
98
{{ py: otype = nptype}}
99
+ cdef object _rand_{{nptype}}_broadcast(object low, object high, object size, aug_state *state, object lock):
100
+ """Array path for 64-bit integer types"""
101
+ cdef np.ndarray low_arr, high_arr, out_arr, highm1_arr
102
+ cdef np.npy_intp i, cnt
103
+ cdef np.broadcast it
104
+ cdef object closed_upper
105
+ cdef uint64_t *out_data
106
+ cdef {{nptype}}_t *highm1_data
107
+ cdef {{nptype}}_t low_v, high_v
108
+ cdef uint64_t rng, last_rng, val, mask, off, out_val
109
+
110
+ low_arr = <np.ndarray>low
111
+ high_arr = <np.ndarray>high
112
+
113
+ if np.any(np.less(low_arr, {{lb}})):
114
+ raise ValueError('low is out of bounds for {{nptype}}')
115
+
116
+ highm1_arr = <np.ndarray>np.empty_like(high_arr, dtype=np.{{nptype}})
117
+ highm1_data = <{{nptype}}_t *>np.PyArray_DATA(highm1_arr)
118
+ cnt = np.PyArray_SIZE(high_arr)
119
+ flat = high_arr.flat
120
+ for i in range(cnt):
121
+ closed_upper = int(flat[i]) - 1
122
+ if closed_upper > {{ub}}:
123
+ raise ValueError('high is out of bounds for {{nptype}}')
124
+ if closed_upper < {{lb}}:
125
+ raise ValueError('low >= high')
126
+ highm1_data[i] = <{{nptype}}_t>closed_upper
127
+
128
+ if np.any(np.greater(low_arr, highm1_arr)):
129
+ raise ValueError('low >= high')
130
+
131
+ high_arr = highm1_arr
132
+ low_arr = <np.ndarray>np.PyArray_FROM_OTF(low, np.{{npctype}}, np.NPY_ALIGNED | np.NPY_FORCECAST)
133
+
134
+ if size is not None:
135
+ out_arr = <np.ndarray>np.empty(size, np.{{nptype}})
136
+ else:
137
+ it = np.PyArray_MultiIterNew2(low_arr, high_arr)
138
+ out_arr = <np.ndarray>np.empty(it.shape, np.{{nptype}})
139
+
140
+ it = np.PyArray_MultiIterNew3(low_arr, high_arr, out_arr)
141
+ out_data = <uint64_t *>np.PyArray_DATA(out_arr)
142
+ n = np.PyArray_SIZE(out_arr)
143
+ mask = last_rng = 0
144
+ with lock, nogil:
145
+ for i in range(n):
146
+ low_v = (<{{nptype}}_t*>np.PyArray_MultiIter_DATA(it, 0))[0]
147
+ high_v = (<{{nptype}}_t*>np.PyArray_MultiIter_DATA(it, 1))[0]
148
+ rng = <{{utype}}_t>(high_v - low_v) # No -1 here since implemented above
149
+ off = <{{utype}}_t>(<{{nptype}}_t>low_v)
150
+
151
+ if rng != last_rng:
152
+ mask = _gen_mask(rng)
153
+ out_data[i] = random_bounded_uint64(state, off, rng, mask)
154
+
155
+ np.PyArray_MultiIter_NEXT(it)
156
+
157
+ return out_arr
158
+ {{endfor}}
159
+
160
+ {{
161
+ py:
162
+ type_info = (('uint64', 'uint64', '0x0ULL', '0xFFFFFFFFFFFFFFFFULL'),
163
+ ('uint32', 'uint32', '0x0UL', '0XFFFFFFFFUL'),
164
+ ('uint16', 'uint16', '0x0UL', '0XFFFFUL'),
165
+ ('uint8', 'uint8', '0x0UL', '0XFFUL'),
166
+ ('bool', 'bool', '0x0UL', '0x1UL'),
167
+ ('int64', 'uint64', '-0x8000000000000000LL', '0x7FFFFFFFFFFFFFFFL'),
168
+ ('int32', 'uint32', '-0x80000000L', '0x7FFFFFFFL'),
169
+ ('int16', 'uint16', '-0x8000L', '0x7FFFL' ),
170
+ ('int8', 'uint8', '-0x80L', '0x7FL' )
171
+ )}}
172
+ {{for nptype, utype, lb, ub in type_info}}
173
+ {{ py: otype = nptype + '_' if nptype == 'bool' else nptype }}
158
174
cdef object _rand_{{nptype}}(object low, object high, object size, aug_state *state, object lock):
159
175
"""
160
176
_rand_{{nptype}}(low, high, size, *state, lock)
@@ -190,30 +206,30 @@ cdef object _rand_{{nptype}}(object low, object high, object size, aug_state *st
190
206
`size`-shaped array of random integers from the appropriate
191
207
distribution, or a single such random int if `size` not provided.
192
208
"""
193
- cdef np.ndarray low_arr, high_arr, out_arr, highm1_arr
209
+ cdef np.ndarray out_arr, low_arr, high_arr
210
+ cdef {{utype}}_t rng, off, out_val
211
+ cdef {{utype}}_t *out_data
194
212
cdef np.npy_intp i, cnt
195
- cdef np.broadcast it
196
- cdef object closed_upper
197
- cdef uint64_t *out_data
198
- cdef {{nptype}}_t *highm1_data
199
- cdef {{nptype}}_t low_v, high_v
200
- cdef uint64_t rng, last_rng, val, mask, off, out_val
201
213
202
- low = np.array(low, copy=False)
203
- high = np.array(high, copy=False)
204
- low_ndim = np.PyArray_NDIM(<np.ndarray>low)
205
- high_ndim = np.PyArray_NDIM(<np.ndarray>high)
206
- if ((low_ndim == 0 or (low_ndim==1 and low.size==1 and size is not None)) and
207
- (high_ndim == 0 or (high_ndim==1 and high.size==1 and size is not None))):
208
- low = int(low)
209
- high = int(high)
210
- high -= 1 # Use a closed interval
214
+ if size is not None:
215
+ if (np.prod(size) == 0):
216
+ return np.empty(size, dtype=np.{{nptype}})
217
+
218
+ low_arr = <np.ndarray>np.array(low, copy=False)
219
+ high_arr = <np.ndarray>np.array(high, copy=False)
220
+ low_ndim = np.PyArray_NDIM(low_arr)
221
+ high_ndim = np.PyArray_NDIM(high_arr)
222
+ if ((low_ndim == 0 or (low_ndim==1 and low_arr.size==1 and size is not None)) and
223
+ (high_ndim == 0 or (high_ndim==1 and high_arr.size==1 and size is not None))):
224
+ low = int(low_arr)
225
+ high = int(high_arr)
226
+ high -= 1
211
227
212
228
if low < {{lb}}:
213
229
raise ValueError("low is out of bounds for {{nptype}}")
214
230
if high > {{ub}}:
215
231
raise ValueError("high is out of bounds for {{nptype}}")
216
- if low > high:
232
+ if low > high: # -1 already subtracted, closed interval
217
233
raise ValueError("low >= high")
218
234
219
235
rng = <{{utype}}_t>(high - low)
@@ -229,53 +245,5 @@ cdef object _rand_{{nptype}}(object low, object high, object size, aug_state *st
229
245
with lock, nogil:
230
246
random_bounded_{{utype}}_fill(state, off, rng, cnt, out_data)
231
247
return out_arr
232
-
233
- low_arr = <np.ndarray>low
234
- high_arr = <np.ndarray>high
235
-
236
- if np.any(np.less(low_arr, {{lb}})):
237
- raise ValueError('low is out of bounds for {{nptype}}')
238
-
239
- highm1_arr = <np.ndarray>np.empty_like(high_arr, dtype=np.{{nptype}})
240
- highm1_data = <{{nptype}}_t *>np.PyArray_DATA(highm1_arr)
241
- n = np.PyArray_SIZE(high_arr)
242
- flat = high_arr.flat
243
- for i in range(n):
244
- closed_upper = int(flat[i]) - 1
245
- if closed_upper > {{ub}}:
246
- raise ValueError('high is out of bounds for {{nptype}}')
247
- if closed_upper < {{lb}}:
248
- raise ValueError('low >= high')
249
- highm1_data[i] = <{{nptype}}_t>closed_upper
250
-
251
- if np.any(np.greater(low_arr, highm1_arr)):
252
- raise ValueError('low >= high')
253
-
254
- high_arr = highm1_arr
255
- low_arr = <np.ndarray>np.PyArray_FROM_OTF(low, np.{{npctype}}, np.NPY_ALIGNED | np.NPY_FORCECAST)
256
-
257
- if size is not None:
258
- out_arr = <np.ndarray>np.empty(size, np.{{nptype}})
259
- else:
260
- it = np.PyArray_MultiIterNew2(low_arr, high_arr)
261
- out_arr = <np.ndarray>np.empty(it.shape, np.{{nptype}})
262
-
263
- it = np.PyArray_MultiIterNew3(low_arr, high_arr, out_arr)
264
- out_data = <uint64_t *>np.PyArray_DATA(out_arr)
265
- n = np.PyArray_SIZE(out_arr)
266
- mask = last_rng = 0
267
- with lock, nogil:
268
- for i in range(n):
269
- low_v = (<{{nptype}}_t*>np.PyArray_MultiIter_DATA(it, 0))[0]
270
- high_v = (<{{nptype}}_t*>np.PyArray_MultiIter_DATA(it, 1))[0]
271
- rng = <{{utype}}_t>(high_v - low_v) # No -1 here since implemented above
272
- off = <{{utype}}_t>(<{{nptype}}_t>low_v)
273
-
274
- if rng != last_rng:
275
- mask = _gen_mask(rng)
276
- out_data[i] = random_bounded_uint64(state, off, rng, mask)
277
-
278
- np.PyArray_MultiIter_NEXT(it)
279
-
280
- return out_arr
248
+ return _rand_{{nptype}}_broadcast(low_arr, high_arr, size, state, lock)
281
249
{{endfor}}
0 commit comments