From f0f2a82a9d0d252754e195e0a02baf7bef942c9b Mon Sep 17 00:00:00 2001 From: Samir Nasibli Date: Fri, 22 Jan 2021 11:04:01 -0600 Subject: [PATCH] ENH: impl random.logistic --- dpnp/backend/include/dpnp_iface.hpp | 16 +++++++++++++ dpnp/backend/include/dpnp_iface_fptr.hpp | 1 + dpnp/backend/kernels/dpnp_krnl_random.cpp | 26 ++++++++++++++++++++ dpnp/dpnp_algo/dpnp_algo.pxd | 1 + dpnp/random/dpnp_algo_random.pyx | 28 ++++++++++++++++++++++ dpnp/random/dpnp_iface_random.py | 29 +++++++++++++++++++---- tests/test_random.py | 22 +++++++++++++++++ tests_external/skipped_tests_numpy.tbl | 2 ++ 8 files changed, 121 insertions(+), 4 deletions(-) diff --git a/dpnp/backend/include/dpnp_iface.hpp b/dpnp/backend/include/dpnp_iface.hpp index c2a3c0f4d94..7762270c5ca 100644 --- a/dpnp/backend/include/dpnp_iface.hpp +++ b/dpnp/backend/include/dpnp_iface.hpp @@ -773,6 +773,22 @@ INP_DLLEXPORT void dpnp_rng_gumbel_c(void* result, double loc, double scale, siz template INP_DLLEXPORT void dpnp_rng_laplace_c(void* result, double loc, double scale, size_t size); +/** + * @ingroup BACKEND_API + * @brief math library implementation of random number generator (logistic distribution) + * + * @param [in] size Number of elements in `result` arrays. + * + * @param [in] loc The position of the distribution peak. + * + * @param [in] scale The exponential decay. + * + * @param [out] result Output array. + * + */ +template +INP_DLLEXPORT void dpnp_rng_logistic_c(void* result, double loc, double scale, size_t size); + /** * @ingroup BACKEND_API * @brief math library implementation of random number generator (lognormal distribution) diff --git a/dpnp/backend/include/dpnp_iface_fptr.hpp b/dpnp/backend/include/dpnp_iface_fptr.hpp index 2f8a043d8da..c7aef8d6955 100644 --- a/dpnp/backend/include/dpnp_iface_fptr.hpp +++ b/dpnp/backend/include/dpnp_iface_fptr.hpp @@ -132,6 +132,7 @@ enum class DPNPFuncName : size_t DPNP_FN_RNG_GUMBEL, /**< Used in numpy.random.gumbel() implementation */ DPNP_FN_RNG_HYPERGEOMETRIC, /**< Used in numpy.random.hypergeometric() implementation */ DPNP_FN_RNG_LAPLACE, /**< Used in numpy.random.laplace() implementation */ + DPNP_FN_RNG_LOGISTIC, /**< Used in numpy.random.logistic() implementation */ DPNP_FN_RNG_LOGNORMAL, /**< Used in numpy.random.lognormal() implementation */ DPNP_FN_RNG_MULTINOMIAL, /**< Used in numpy.random.multinomial() implementation */ DPNP_FN_RNG_MULTIVARIATE_NORMAL, /**< Used in numpy.random.multivariate_normal() implementation */ diff --git a/dpnp/backend/kernels/dpnp_krnl_random.cpp b/dpnp/backend/kernels/dpnp_krnl_random.cpp index 8bebd824abb..fab3072d9f5 100644 --- a/dpnp/backend/kernels/dpnp_krnl_random.cpp +++ b/dpnp/backend/kernels/dpnp_krnl_random.cpp @@ -301,6 +301,30 @@ void dpnp_rng_laplace_c(void* result, double loc, double scale, size_t size) event_out.wait(); } +/* Logistic(loc, scale) ~ loc + scale * log(u/(1.0 - u)) */ +template +void dpnp_rng_logistic_c(void* result, double loc, double scale, size_t size) +{ + if (!size) + { + return; + } + cl::sycl::vector_class no_deps; + + const _DataType d_zero = _DataType(0.0); + const _DataType d_one = _DataType(1.0); + + _DataType* result1 = reinterpret_cast<_DataType*>(result); + + mkl_rng::uniform<_DataType> distribution(d_zero, d_one); + auto event_out = mkl_rng::generate(distribution, DPNP_RNG_ENGINE, size, result1); + event_out.wait(); + + for(size_t i = 0; i < size; i++) result1[i] = log(result1[i]/(1.0 - result1[i])); + + for(size_t i = 0; i < size; i++) result1[i] = loc + scale * result1[i]; +} + template void dpnp_rng_lognormal_c(void* result, _DataType mean, _DataType stddev, size_t size) { @@ -606,6 +630,8 @@ void func_map_init_random(func_map_t& fmap) fmap[DPNPFuncName::DPNP_FN_RNG_LAPLACE][eft_DBL][eft_DBL] = {eft_DBL, (void*)dpnp_rng_laplace_c}; + fmap[DPNPFuncName::DPNP_FN_RNG_LOGISTIC][eft_DBL][eft_DBL] = {eft_DBL, (void*)dpnp_rng_logistic_c}; + fmap[DPNPFuncName::DPNP_FN_RNG_LOGNORMAL][eft_DBL][eft_DBL] = {eft_DBL, (void*)dpnp_rng_lognormal_c}; fmap[DPNPFuncName::DPNP_FN_RNG_MULTINOMIAL][eft_INT][eft_INT] = {eft_INT, (void*)dpnp_rng_multinomial_c}; diff --git a/dpnp/dpnp_algo/dpnp_algo.pxd b/dpnp/dpnp_algo/dpnp_algo.pxd index 931b04b5396..af79c92f92d 100644 --- a/dpnp/dpnp_algo/dpnp_algo.pxd +++ b/dpnp/dpnp_algo/dpnp_algo.pxd @@ -105,6 +105,7 @@ cdef extern from "dpnp_iface_fptr.hpp" namespace "DPNPFuncName": # need this na DPNP_FN_RNG_GUMBEL DPNP_FN_RNG_HYPERGEOMETRIC DPNP_FN_RNG_LAPLACE + DPNP_FN_RNG_LOGISTIC DPNP_FN_RNG_LOGNORMAL DPNP_FN_RNG_MULTINOMIAL DPNP_FN_RNG_MULTIVARIATE_NORMAL diff --git a/dpnp/random/dpnp_algo_random.pyx b/dpnp/random/dpnp_algo_random.pyx index 825780ff281..62634cb7121 100644 --- a/dpnp/random/dpnp_algo_random.pyx +++ b/dpnp/random/dpnp_algo_random.pyx @@ -53,6 +53,7 @@ __all__ = [ "dpnp_hypergeometric", "dpnp_laplace", "dpnp_lognormal", + "dpnp_rng_logistic", "dpnp_multinomial", "dpnp_multivariate_normal", "dpnp_negative_binomial", @@ -81,6 +82,7 @@ ctypedef void(*fptr_dpnp_rng_gaussian_c_1out_t)(void *, double, double, size_t) ctypedef void(*fptr_dpnp_rng_gumbel_c_1out_t)(void *, double, double, size_t) except + ctypedef void(*fptr_dpnp_rng_hypergeometric_c_1out_t)(void *, int, int, int, size_t) except + ctypedef void(*fptr_dpnp_rng_laplace_c_1out_t)(void *, double, double, size_t) except + +ctypedef void(*fptr_dpnp_rng_logistic_c_1out_t)(void *, double, double, size_t) except + ctypedef void(*fptr_dpnp_rng_lognormal_c_1out_t)(void *, double, double, size_t) except + ctypedef void(*fptr_dpnp_rng_multinomial_c_1out_t)(void * result, int, const double *, const size_t, size_t) except + ctypedef void(*fptr_dpnp_rng_multivariate_normal_c_1out_t)(void *, @@ -396,6 +398,32 @@ cpdef dparray dpnp_laplace(double loc, double scale, size): return result +cpdef dparray dpnp_rng_logistic(double loc, double scale, size): + """ + Returns an array populated with samples from logistic distribution. + `dpnp_rng_logistic` generates a matrix filled with random floats sampled from a + univariate logistic distribution. + + """ + + # convert string type names (dparray.dtype) to C enum DPNPFuncType + cdef DPNPFuncType param1_type = dpnp_dtype_to_DPNPFuncType(numpy.float64) + + # get the FPTR data structure + cdef DPNPFuncData kernel_data = get_dpnp_function_ptr(DPNP_FN_RNG_LOGISTIC, param1_type, param1_type) + + result_type = dpnp_DPNPFuncType_to_dtype( < size_t > kernel_data.return_type) + # ceate result array with type given by FPTR data + cdef dparray result = dparray(size, dtype=result_type) + + cdef fptr_dpnp_rng_logistic_c_1out_t func = < fptr_dpnp_rng_logistic_c_1out_t > kernel_data.ptr + # call FPTR function + func(result.get_data(), loc, scale, result.size) + + return result + + + cpdef dparray dpnp_lognormal(double mean, double stddev, size): """ Returns an array populated with samples from lognormal distribution. diff --git a/dpnp/random/dpnp_iface_random.py b/dpnp/random/dpnp_iface_random.py index 744c45a9cc3..d750153f81e 100644 --- a/dpnp/random/dpnp_iface_random.py +++ b/dpnp/random/dpnp_iface_random.py @@ -527,13 +527,34 @@ def logistic(loc=0.0, scale=1.0, size=None): For full documentation refer to :obj:`numpy.random.logistic`. - Notes - ----- - The function uses `numpy.random.logistic` on the backend and will be - executed on fallback backend. + Limitations + ----------- + Parameters ``loc`` and ``scale`` are supported as scalar. + Otherwise, :obj:`numpy.random.logistic(loc, scale, size)` samples are drawn. + Output array data type is :obj:`dpnp.float64`. + + Examples + -------- + >>> loc, scale = 0., 1. + >>> s = dpnp.random.logistic(loc, scale, 1000) """ + if not use_origin_backend(loc): + # TODO: + # array_like of floats for `loc` and `scale` + if not dpnp.isscalar(loc): + pass + elif not dpnp.isscalar(scale): + pass + elif scale < 0: + pass + else: + if size == None or size == 1: + return dpnp_rng_logistic(loc, scale, size)[0] + else: + return dpnp_rng_logistic(loc, scale, size) + return call_origin(numpy.random.logistic, loc, scale, size) diff --git a/tests/test_random.py b/tests/test_random.py index cb136b4b055..c4a477c7e7e 100644 --- a/tests/test_random.py +++ b/tests/test_random.py @@ -395,6 +395,28 @@ def test_seed(self): self.check_seed('laplace', {'loc': loc, 'scale': scale}) +class TestDistributionsLogistic(TestDistribution): + + def test_moments(self): + loc = 2.56 + scale = 0.8 + expected_mean = loc + expected_var = (scale ** 2) * (numpy.pi ** 2) / 3 + self.check_moments('logistic', expected_mean, + expected_var, {'loc': loc, 'scale': scale}) + + def test_invalid_args(self): + loc = 3.0 # OK + scale = -1.0 # non-negative `scale` is expected + self.check_invalid_args('logistic', + {'loc': loc, 'scale': scale}) + + def test_seed(self): + loc = 2.56 + scale = 0.8 + self.check_seed('logistic', {'loc': loc, 'scale': scale}) + + class TestDistributionsLognormal(TestDistribution): def test_extreme_value(self): diff --git a/tests_external/skipped_tests_numpy.tbl b/tests_external/skipped_tests_numpy.tbl index 62728216eb4..f4a1477e8da 100644 --- a/tests_external/skipped_tests_numpy.tbl +++ b/tests_external/skipped_tests_numpy.tbl @@ -1474,6 +1474,7 @@ tests/test_random.py::TestRandomDist::test_gumbel_0 tests/test_random.py::TestRandomDist::test_hypergeometric tests/test_random.py::TestRandomDist::test_laplace tests/test_random.py::TestRandomDist::test_laplace_0 +tests/test_random.py::TestRandomDist::test_logistic tests/test_random.py::TestRandomDist::test_lognormal tests/test_random.py::TestRandomDist::test_lognormal_0 tests/test_random.py::TestRandomDist::test_multinomial @@ -1565,6 +1566,7 @@ tests/test_randomstate.py::TestRandomDist::test_gumbel_0 tests/test_randomstate.py::TestRandomDist::test_hypergeometric tests/test_randomstate.py::TestRandomDist::test_laplace tests/test_randomstate.py::TestRandomDist::test_laplace_0 +tests/test_randomstate.py::TestRandomDist::test_logistic tests/test_randomstate.py::TestRandomDist::test_lognormal tests/test_randomstate.py::TestRandomDist::test_lognormal_0 tests/test_randomstate.py::TestRandomDist::test_multinomial