Skip to content

Commit b31de2e

Browse files
authored
Merge branch 'master' into fix/validate-brng-token
2 parents 4609673 + 104d99b commit b31de2e

3 files changed

Lines changed: 363 additions & 18 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
* Added a thread-safety section to the how-to guide for free-threaded Python [gh-159](https://github.com/IntelPython/mkl_random/pull/159)
1313

1414
### Changed
15+
* Sped up `normal`, `uniform`, `exponential`, `laplace`, `gumbel`, `logistic`, `rayleigh` and `lognormal` for array-valued parameters. Seeded results change for these array paths; scalar paths are unchanged [gh-171](https://github.com/IntelPython/mkl_random/pull/171)
16+
* Array parameters for these distributions must broadcast to the requested `size` without adding dimensions; previously accepted mismatches now raise `ValueError` [gh-171](https://github.com/IntelPython/mkl_random/pull/171)
17+
* `uniform` with array-valued bounds may return `high` due to floating-point rounding [gh-171](https://github.com/IntelPython/mkl_random/pull/171)
1518
* Pinned Cython in the Coverity Scan workflow so generated code stays stable between scans, and added `coverity/README.md` documenting the known Cython-boilerplate false positives and the scan review checklist [gh-164](https://github.com/IntelPython/mkl_random/pull/164)
1619
* Sped up `randint` for power-of-two ranges at or above `INT_MAX` [gh-172](https://github.com/IntelPython/mkl_random/pull/172)
1720
* The random streams for power-of-two `randint` ranges at or above `INT_MAX` have changed: with a fixed seed these now produce different (but equally valid) samples. All other streams are unaffected. [gh-172](https://github.com/IntelPython/mkl_random/pull/172)

‎mkl_random/mklrand.pyx‎

Lines changed: 167 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,150 @@ cdef object vec_cont2_array(
759759
return arr_obj
760760

761761

762+
cdef object _param_out_shape(object size, tuple param_shapes):
763+
"""Result shape for a parameterised draw, matching the per-element paths."""
764+
cdef object out_shape
765+
cdef object bshape
766+
767+
if size is None:
768+
return np.broadcast_shapes(*param_shapes)
769+
770+
out_shape = tuple(size) if np.iterable(size) else (size,)
771+
try:
772+
bshape = np.broadcast_shapes(out_shape, *param_shapes)
773+
except ValueError:
774+
raise ValueError("size is not compatible with inputs")
775+
if bshape != out_shape:
776+
raise ValueError("size is not compatible with inputs")
777+
return out_shape
778+
779+
780+
cdef object _fill_standard2(
781+
irk_state *state,
782+
irk_cont2_vec func,
783+
object out_shape,
784+
object lock
785+
):
786+
"""Fill an entire request with one call, using standard parameters."""
787+
cdef cnp.ndarray array
788+
cdef cnp.npy_intp n
789+
cdef double *array_data
790+
791+
array = <cnp.ndarray>np.empty(out_shape, np.float64)
792+
n = cnp.PyArray_SIZE(array)
793+
if n:
794+
array_data = <double *>cnp.PyArray_DATA(array)
795+
with lock, nogil:
796+
func(state, n, array_data, 0.0, 1.0)
797+
return array
798+
799+
800+
cdef object vec_loc_scale_array(
801+
irk_state *state,
802+
irk_cont2_vec func,
803+
object size,
804+
cnp.ndarray oloc,
805+
cnp.ndarray oscale,
806+
object lock
807+
):
808+
"""Draw a location and scale family with array-valued parameters.
809+
810+
``func(0.0, 1.0)`` yields the standardised member, so
811+
``loc + scale * standardised`` is exact and needs one call per request.
812+
"""
813+
cdef object array
814+
815+
array = _fill_standard2(
816+
state,
817+
func,
818+
_param_out_shape(
819+
size, ((<object>oloc).shape, (<object>oscale).shape)
820+
),
821+
lock
822+
)
823+
np.multiply(array, oscale, out=array)
824+
np.add(array, oloc, out=array)
825+
return array
826+
827+
828+
cdef object vec_scale_array(
829+
irk_state *state,
830+
irk_cont1_vec func,
831+
object size,
832+
cnp.ndarray oscale,
833+
object lock
834+
):
835+
"""Draw a scale family with an array-valued scale, one call per request."""
836+
cdef cnp.ndarray array
837+
cdef cnp.npy_intp n
838+
cdef double *array_data
839+
840+
array = <cnp.ndarray>np.empty(
841+
_param_out_shape(size, ((<object>oscale).shape,)), np.float64
842+
)
843+
n = cnp.PyArray_SIZE(array)
844+
if n:
845+
array_data = <double *>cnp.PyArray_DATA(array)
846+
with lock, nogil:
847+
func(state, n, array_data, 1.0)
848+
np.multiply(array, oscale, out=array)
849+
return array
850+
851+
852+
cdef object vec_uniform_array(
853+
irk_state *state,
854+
irk_cont2_vec func,
855+
object size,
856+
cnp.ndarray olow,
857+
cnp.ndarray ohigh,
858+
object lock
859+
):
860+
"""Draw uniforms over array-valued bounds, one call per request."""
861+
cdef object array
862+
863+
array = _fill_standard2(
864+
state,
865+
func,
866+
_param_out_shape(
867+
size, ((<object>olow).shape, (<object>ohigh).shape)
868+
),
869+
lock
870+
)
871+
np.multiply(array, np.subtract(ohigh, olow), out=array)
872+
np.add(array, olow, out=array)
873+
return array
874+
875+
876+
cdef object vec_lognormal_array(
877+
irk_state *state,
878+
irk_cont2_vec normal_func,
879+
object size,
880+
cnp.ndarray omean,
881+
cnp.ndarray osigma,
882+
object lock
883+
):
884+
"""Draw lognormals with array-valued parameters, one call per request.
885+
886+
Uses the normal fill: the parameters sit inside the exponential, so no
887+
affine step applies to a standardised lognormal,
888+
but exp(mean + sigma * z) does.
889+
"""
890+
cdef object array
891+
892+
array = _fill_standard2(
893+
state,
894+
normal_func,
895+
_param_out_shape(
896+
size, ((<object>omean).shape, (<object>osigma).shape)
897+
),
898+
lock
899+
)
900+
np.multiply(array, osigma, out=array)
901+
np.add(array, omean, out=array)
902+
np.exp(array, out=array)
903+
return array
904+
905+
762906
cdef object vec_cont3_array_sc(
763907
irk_state *state,
764908
irk_cont3_vec func,
@@ -2742,16 +2886,19 @@ cdef class _MKLRandomState:
27422886
Samples are uniformly distributed over the half-open interval
27432887
``[low, high)`` (includes low, but excludes high). In other words,
27442888
any value within the given interval is equally likely to be drawn
2745-
by `uniform`.
2889+
by `uniform`. With array-valued bounds, floating-point rounding
2890+
may include the upper boundary in the returned samples.
27462891
27472892
Parameters
27482893
----------
27492894
low : float, optional
27502895
Lower boundary of the output interval. All values generated will be
27512896
greater than or equal to low. The default value is 0.
27522897
high : float
2753-
Upper boundary of the output interval. All values generated will be
2754-
less than high. The default value is 1.0.
2898+
Upper boundary of the output interval. With array-valued bounds,
2899+
high may be included due to floating-point rounding in
2900+
``low + (high - low) * U``, where ``U`` is drawn from ``[0, 1)``.
2901+
The default value is 1.0.
27552902
size : int or tuple of ints, optional
27562903
Output shape. If the given shape is, e.g., ``(m, n, k)``, then
27572904
``m * n * k`` samples are drawn. Default is None, in which case a
@@ -2836,7 +2983,7 @@ cdef class _MKLRandomState:
28362983
if np.any(olow >= ohigh):
28372984
raise ValueError("low >= high")
28382985

2839-
return vec_cont2_array(
2986+
return vec_uniform_array(
28402987
self.internal_state, irk_uniform_vec, size, olow, ohigh, self.lock
28412988
)
28422989

@@ -3238,23 +3385,23 @@ cdef class _MKLRandomState:
32383385
method, [ICDF, BOXMULLER, BOXMULLER2], _method_alias_dict_gaussian
32393386
)
32403387
if method is ICDF:
3241-
return vec_cont2_array(
3388+
return vec_loc_scale_array(
32423389
self.internal_state,
32433390
irk_normal_vec_ICDF,
32443391
size,
32453392
oloc,
32463393
oscale, self.lock
32473394
)
32483395
elif method is BOXMULLER2:
3249-
return vec_cont2_array(
3396+
return vec_loc_scale_array(
32503397
self.internal_state,
32513398
irk_normal_vec_BM2,
32523399
size,
32533400
oloc,
32543401
oscale, self.lock
32553402
)
32563403
else:
3257-
return vec_cont2_array(
3404+
return vec_loc_scale_array(
32583405
self.internal_state,
32593406
irk_normal_vec_BM1,
32603407
size,
@@ -3393,7 +3540,7 @@ cdef class _MKLRandomState:
33933540

33943541
if np.any(np.signbit(oscale) | (oscale == 0)):
33953542
raise ValueError("scale <= 0")
3396-
return vec_cont1_array(
3543+
return vec_scale_array(
33973544
self.internal_state, irk_exponential_vec, size, oscale, self.lock
33983545
)
33993546

@@ -4838,8 +4985,9 @@ cdef class _MKLRandomState:
48384985

48394986
if np.any(np.signbit(oscale) | np.equal(oscale, 0.0)):
48404987
raise ValueError("scale <= 0")
4841-
return vec_cont2_array(
4842-
self.internal_state, irk_laplace_vec, size, oloc, oscale, self.lock
4988+
return vec_loc_scale_array(
4989+
self.internal_state, irk_laplace_vec, size, oloc, oscale,
4990+
self.lock
48434991
)
48444992

48454993
def gumbel(self, loc=0.0, scale=1.0, size=None):
@@ -4978,8 +5126,9 @@ cdef class _MKLRandomState:
49785126

49795127
if np.any(np.signbit(oscale) | np.equal(oscale, 0.0)):
49805128
raise ValueError("scale <= 0")
4981-
return vec_cont2_array(
4982-
self.internal_state, irk_gumbel_vec, size, oloc, oscale, self.lock
5129+
return vec_loc_scale_array(
5130+
self.internal_state, irk_gumbel_vec, size, oloc, oscale,
5131+
self.lock
49835132
)
49845133

49855134
def logistic(self, loc=0.0, scale=1.0, size=None):
@@ -5079,7 +5228,7 @@ cdef class _MKLRandomState:
50795228

50805229
if np.any(np.signbit(oscale) | np.equal(oscale, 0.0)):
50815230
raise ValueError("scale <= 0")
5082-
return vec_cont2_array(
5231+
return vec_loc_scale_array(
50835232
self.internal_state,
50845233
irk_logistic_vec,
50855234
size,
@@ -5240,18 +5389,18 @@ cdef class _MKLRandomState:
52405389
method, [ICDF, BOXMULLER], _method_alias_dict_gaussian_short
52415390
)
52425391
if method is ICDF:
5243-
return vec_cont2_array(
5392+
return vec_lognormal_array(
52445393
self.internal_state,
5245-
irk_lognormal_vec_ICDF,
5394+
irk_normal_vec_ICDF,
52465395
size,
52475396
omean,
52485397
osigma,
52495398
self.lock
52505399
)
52515400
else:
5252-
return vec_cont2_array(
5401+
return vec_lognormal_array(
52535402
self.internal_state,
5254-
irk_lognormal_vec_BM,
5403+
irk_normal_vec_BM2,
52555404
size,
52565405
omean,
52575406
osigma,
@@ -5333,7 +5482,7 @@ cdef class _MKLRandomState:
53335482

53345483
if np.any(np.signbit(oscale) | np.equal(oscale, 0.0)):
53355484
raise ValueError("scale <= 0.0")
5336-
return vec_cont1_array(
5485+
return vec_scale_array(
53375486
self.internal_state, irk_rayleigh_vec, size, oscale, self.lock
53385487
)
53395488

0 commit comments

Comments
 (0)