Skip to content

Commit bb0eae2

Browse files
committed
Add integers and the discrete distributions to dpnp.random.Generator
Add integers and the poisson, binomial, negative_binomial, geometric, logseries, zipf and hypergeometric samplers, along with noncentral_chisquare and noncentral_f, which need poisson. The discrete samplers return int64 and compute in double precision, or in single precision on devices without fp64, where host parameters are clipped to stay inside their domain once rounded.
1 parent 0160558 commit bb0eae2

23 files changed

Lines changed: 2511 additions & 27 deletions

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
* Added `dpnp.top_k` to return the `k` largest or smallest elements and their indices along an axis, with a `sorted` keyword to return them in sorted order [#3133](https://github.com/IntelPython/dpnp/pull/3133)
1313
* Added the `dpnp.random.Generator` API with `default_rng`, `BitGenerator`, `SeedSequence` and the `Philox4x3210` bit generator, sampling on the device with the oneMKL device RNG API [#3157](https://github.com/IntelPython/dpnp/pull/3157)
1414
* Added the continuous distributions of `numpy.random.Generator`, such as `gamma`, `beta` and `vonmises`, to `dpnp.random.Generator` [#3158](https://github.com/IntelPython/dpnp/pull/3158)
15+
* Added `integers` and the discrete distributions of `numpy.random.Generator`, such as `poisson`, `binomial` and `hypergeometric`, to `dpnp.random.Generator`, along with `noncentral_chisquare` and `noncentral_f` [#3159](https://github.com/IntelPython/dpnp/pull/3159)
1516

1617
### Changed
1718

‎doc/reference/random.rst‎

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,21 @@ host-side oneMKL RNG.
1717
1818
rng = dpnp.random.default_rng(seed=42)
1919
x = rng.standard_normal((3, 4)) # on the default device
20-
y = rng.random(5, usm_type="shared")
20+
y = rng.integers(0, 10, size=5, usm_type="shared")
2121
2222
rng = dpnp.random.default_rng(42, device="cpu")
2323
2424
A generator draws all its samples on one SYCL queue, chosen with the
2525
``device`` or ``sycl_queue`` keyword when it is created, and the samples
26-
depend only on the seed, not on the device. All samples are
26+
depend only on the seed, not on the device, except that the discrete
27+
distributions also depend on its support of double precision. All samples are
2728
:class:`dpnp.ndarray`, zero-dimensional when ``size`` is ``None``.
2829

2930
Differences from NumPy
3031
----------------------
3132

32-
* The same seed gives the same stream on every device.
33+
* The same seed gives the same stream on every device, but for the discrete
34+
distributions noted below.
3335
The streams differ from NumPy's.
3436
* The floating-point distributions take a ``dtype`` keyword, as in CuPy,
3537
which defaults to the default floating-point type of the device. All
@@ -39,7 +41,20 @@ Differences from NumPy
3941
not, since that would wait for the device.
4042
Invalid ones give undefined values, such as NaN or the smallest ``int64``,
4143
but the kernels handle them safely: they never loop without end or access
42-
memory out of bounds.
44+
memory out of bounds. Floating-point arrays given for integer parameters
45+
are converted as by :meth:`dpnp.ndarray.astype`, whose result for NaN and
46+
out-of-range values is undefined.
47+
* The discrete distributions return ``int64`` values. They compute in double
48+
precision on devices supporting it, and in single precision otherwise.
49+
There, the streams differ from the ones of devices with double precision,
50+
and some samples are only multiples of a power of two: the ones above
51+
``2**24`` of ``geometric``, ``logseries`` and ``zipf``, and the ones of
52+
``poisson`` and ``binomial`` with a standard deviation above about
53+
``2**22``.
54+
* ``zipf`` follows the Zipf distribution truncated to the largest ``int64``.
55+
NumPy's rejection test rounds to accepting every sample above ``2**53``,
56+
which makes its tail too heavy for ``a`` close to 1.
57+
* ``hypergeometric`` requires ``ngood`` and ``nbad`` below ``10**9``.
4358

4459
Random Generator
4560
----------------
@@ -58,6 +73,7 @@ Simple random data
5873
:toctree: generated/
5974
:nosignatures:
6075

76+
Generator.integers
6177
Generator.random
6278

6379
Distributions
@@ -68,16 +84,24 @@ Distributions
6884
:nosignatures:
6985

7086
Generator.beta
87+
Generator.binomial
7188
Generator.chisquare
7289
Generator.exponential
7390
Generator.f
7491
Generator.gamma
92+
Generator.geometric
7593
Generator.gumbel
94+
Generator.hypergeometric
7695
Generator.laplace
7796
Generator.logistic
7897
Generator.lognormal
98+
Generator.logseries
99+
Generator.negative_binomial
100+
Generator.noncentral_chisquare
101+
Generator.noncentral_f
79102
Generator.normal
80103
Generator.pareto
104+
Generator.poisson
81105
Generator.power
82106
Generator.rayleigh
83107
Generator.standard_cauchy
@@ -90,6 +114,7 @@ Distributions
90114
Generator.vonmises
91115
Generator.wald
92116
Generator.weibull
117+
Generator.zipf
93118

94119
Bit generators
95120
--------------

‎dpnp/AGENTS.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
- Extension calls are ordered with `_manager = dpctl.utils.SequentialOrderManager[exec_q]`: pass `depends=_manager.submitted_events`, then `_manager.add_event_pair(ht_ev, ev)`. Don't add blocking waits.
2222
- Use `dpnp.default_float_type(...)` or `map_dtype_to_device(...)` for the default float type.
2323
- Deprecated ufuncs use `DPNPDeprecatedUnaryFunc(..., deprecated_msg=...)`.
24-
- A `dpnp.random` distribution is a `Generator` method: it calls `self._check_queues(...)` on its array parameters (raises `ExecutionPlacementError`), checks host parameters as NumPy does, and draws through `_cont` or `_float_sample` into `BitGenerator._fill`. Parameters on the device are not checked, as the check waits for the device. Temporaries take the output's `usm_type`.
24+
- A `dpnp.random` distribution is a `Generator` method: it calls `self._check_queues(...)` on its array parameters (raises `ExecutionPlacementError`), checks host parameters as NumPy does, and draws through `_cont`, `_float_sample` or `_discrete_sample` into `BitGenerator._fill`. Parameters on the device are not checked, as the check waits for the device. Temporaries take the output's `usm_type`.
2525
- Unsupported NumPy keywords raise `NotImplementedError` (`dpnp.check_limitations`) and are listed under `Limitations` in the docstring.
2626
- The Array API spec version is set in three places that change together: `__array_api_version__` in `dpnp/tensor/_array_api.py`, `ARRAY_API_TESTS_VERSION` in `.github/workflows/conda-package.yml` and `doc/reference/array_api.rst` (plus the version test in `dpnp/tests/test_ndarray.py`). Inspection utilities come from `__array_namespace_info__` in `dpnp_array_api_info.py`.
2727
- `dpnp.tensor` replaces `dpctl.tensor`, which no longer exists here; `SyclQueue`, `SyclDevice`, `dpctl.memory` and `SequentialOrderManager` still come from `dpctl`.

‎dpnp/libtensor/AGENTS.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ A new extension's `CMakeLists.txt` lists its sources and calls `build_dpnp_pybin
2323
- Guard double, complex<double> and half with `dpnp::tensor::type_utils::validate_type_for_device<T>(q)`; don't use double literals or casts in kernels that must run without fp64.
2424
- `blas`, `fft`, `lapack` and `sparse` build against either oneMKL or oneMath (`USE_ONEMATH`); `vm` has kernels only with oneMKL: with oneMath it builds empty and `_is_available()` returns `False`; `rng` uses the header-only oneMKL device RNG API and links no oneMKL library.
2525
- Release the GIL around blocking oneMKL calls (`py::gil_scoped_release`).
26-
- `rng` values depend only on the engine state and the number of elements, never on the device or the layout chosen for it (`extensions/rng/kernels/sample.hpp`); each engine's `items_per_work_item` (`kernels/engines.hpp`) is part of its stream definition. Parameters on the device are unchecked, so a rejection sampler returns NaN for the inputs that would make it loop forever, including values that are valid but round badly in fp32.
26+
- `rng` values depend only on the engine state and the number of elements, never on the device or the layout chosen for it (`extensions/rng/kernels/sample.hpp`); each engine's `items_per_work_item` (`kernels/engines.hpp`) is part of its stream definition. Parameters on the device are unchecked, so a rejection sampler returns NaN or the smallest `int64` for the inputs that would make it loop forever, including values that are valid but round badly in fp32. Use `umul_hi` from `kernels/umul_hi.hpp` instead of `sycl::mul_hi`, which DPC++ 2026.2 miscompiles on Intel GPUs.
2727
- An extension whose kernels may be launched over more than `INT_MAX` work-items passes `LARGE_RANGES` to `build_dpnp_pybind11_ext`, which adds `-fno-sycl-id-queries-fit-in-int` at compile and link time (DPC++ 2026.2 and later need it at link time). `source/CMakeLists.txt` and `dpnp_backend_c` pass it to both `target_compile_options` and `target_link_options`.
2828
- clang-format 23 formats C++ (`.clang-format`: LLVM base, 4-space indent, 80 columns); gersemi formats CMake at 88 columns, except `dpnp/backend/cmake/Modules/`.
2929

‎dpnp/libtensor/extensions/rng/CMakeLists.txt‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,14 @@
2929

3030
set(python_module_name _rng_impl)
3131
set(_module_src
32+
${CMAKE_CURRENT_SOURCE_DIR}/binomial.cpp
33+
${CMAKE_CURRENT_SOURCE_DIR}/discrete.cpp
3234
${CMAKE_CURRENT_SOURCE_DIR}/exponential.cpp
3335
${CMAKE_CURRENT_SOURCE_DIR}/gamma.cpp
36+
${CMAKE_CURRENT_SOURCE_DIR}/integers.cpp
37+
${CMAKE_CURRENT_SOURCE_DIR}/noncentral.cpp
3438
${CMAKE_CURRENT_SOURCE_DIR}/normal.cpp
39+
${CMAKE_CURRENT_SOURCE_DIR}/poisson.cpp
3540
${CMAKE_CURRENT_SOURCE_DIR}/rng_py.cpp
3641
${CMAKE_CURRENT_SOURCE_DIR}/sample.cpp
3742
${CMAKE_CURRENT_SOURCE_DIR}/uniform.cpp
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
//*****************************************************************************
2+
// Copyright (c) 2026, Intel Corporation
3+
// All rights reserved.
4+
//
5+
// Redistribution and use in source and binary forms, with or without
6+
// modification, are permitted provided that the following conditions are met:
7+
// - Redistributions of source code must retain the above copyright notice,
8+
// this list of conditions and the following disclaimer.
9+
// - Redistributions in binary form must reproduce the above copyright notice,
10+
// this list of conditions and the following disclaimer in the documentation
11+
// and/or other materials provided with the distribution.
12+
// - Neither the name of the copyright holder nor the names of its contributors
13+
// may be used to endorse or promote products derived from this software
14+
// without specific prior written permission.
15+
//
16+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17+
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18+
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19+
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20+
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21+
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22+
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23+
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24+
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25+
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
26+
// THE POSSIBILITY OF SUCH DAMAGE.
27+
//*****************************************************************************
28+
#include <pybind11/pybind11.h>
29+
30+
#include "common.hpp"
31+
#include "distributions.hpp"
32+
33+
namespace dpnp::extensions::rng
34+
{
35+
static discrete_dispatch_table_t binomial_dispatch_table;
36+
37+
void init_binomial(py::module_ m)
38+
{
39+
def_discrete_sampler<rng_ns::BinomialSampler>(
40+
m, "_binomial",
41+
"Fill the result with binomial samples of n trials of probability p",
42+
binomial_dispatch_table);
43+
}
44+
} // namespace dpnp::extensions::rng

‎dpnp/libtensor/extensions/rng/common.hpp‎

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ typedef sycl::event (*sample_fn_ptr_t)(sycl::queue &,
8181
using sample_dispatch_table_t =
8282
sample_fn_ptr_t[rng_ns::num_engines][td_ns::num_types];
8383

84+
// Discrete samplers, whose result is always int64, indexed by the engine and
85+
// by whether they compute in double (1) or in float (0)
86+
using discrete_dispatch_table_t = sample_fn_ptr_t[rng_ns::num_engines][2];
87+
8488
namespace detail
8589
{
8690
template <typename T>
@@ -138,8 +142,11 @@ sycl::event submit_sample(sycl::queue &exec_q,
138142
} // namespace detail
139143

140144
// Checks the parameters and the result against SamplerT, then submits the
141-
// kernel, unless there is nothing to sample.
142-
template <typename EngineTraitsT, typename SamplerT>
145+
// kernel, unless there is nothing to sample. ComputeT is the type the
146+
// sampler computes in, which the device must support.
147+
template <typename EngineTraitsT,
148+
typename SamplerT,
149+
typename ComputeT = typename SamplerT::result_type>
143150
sycl::event sample_impl(sycl::queue &exec_q,
144151
const rng_ns::EngineState &state,
145152
const std::size_t nelems,
@@ -162,6 +169,7 @@ sycl::event sample_impl(sycl::queue &exec_q,
162169
}
163170

164171
detail::validate_type_for_device<T>(exec_q);
172+
detail::validate_type_for_device<ComputeT>(exec_q);
165173
detail::validate_types_for_device<param_types>(
166174
exec_q, std::make_index_sequence<n_params>{});
167175

@@ -195,6 +203,12 @@ struct RealTypes
195203
{
196204
};
197205

206+
// Types the integer samplers support
207+
template <typename T>
208+
struct IntegerTypes : std::bool_constant<std::is_integral_v<T>>
209+
{
210+
};
211+
198212
// The raw output of a bit generator, whole or as 32-bit halves
199213
template <typename T>
200214
struct RawTypes : std::bool_constant<std::is_same_v<T, std::uint64_t> ||
@@ -238,11 +252,27 @@ void init_sample_dispatch_table(sample_dispatch_table_t &table)
238252
table[philox]);
239253
}
240254

255+
template <template <typename> class SamplerT>
256+
void init_discrete_dispatch_table(discrete_dispatch_table_t &table)
257+
{
258+
static_assert(rng_ns::num_engines == 1,
259+
"Every engine must have a row in the dispatch table");
260+
261+
using Philox = rng_ns::Philox4x32x10;
262+
constexpr int philox = static_cast<int>(rng_ns::engine_id::philox4x32x10);
263+
table[philox][0] = sample_impl<Philox, SamplerT<float>, float>;
264+
table[philox][1] = sample_impl<Philox, SamplerT<double>, double>;
265+
}
266+
241267
// Looks up the sampler of `result`'s type, and throws if there is none
242268
extern sample_fn_ptr_t lookup_sampler(const sample_dispatch_table_t &table,
243269
const int engine,
244270
const dpnp::tensor::usm_ndarray &result);
245271

272+
extern sample_fn_ptr_t lookup_sampler(const discrete_dispatch_table_t &table,
273+
const int engine,
274+
const bool double_precision);
275+
246276
extern std::pair<sycl::event, sycl::event>
247277
py_sample(sycl::queue &exec_q,
248278
const std::vector<std::uint64_t> &seed,
@@ -281,4 +311,34 @@ void def_sampler(py::module_ &m,
281311
py::arg("params"), py::arg("result"),
282312
py::arg("depends") = py::list());
283313
}
314+
315+
// As def_sampler, for a discrete sampler, whose Python function also takes
316+
// whether to compute in double precision
317+
template <template <typename> class SamplerT>
318+
void def_discrete_sampler(py::module_ &m,
319+
const char *name,
320+
const char *doc,
321+
discrete_dispatch_table_t &table)
322+
{
323+
init_discrete_dispatch_table<SamplerT>(table);
324+
325+
auto sample_pyapi = [&table](sycl::queue &exec_q, const int engine,
326+
const std::vector<std::uint64_t> &seed,
327+
const std::uint64_t counter_lo,
328+
const std::uint64_t counter_hi,
329+
const py::sequence &params,
330+
const dpnp::tensor::usm_ndarray &result,
331+
const bool double_precision,
332+
const std::vector<sycl::event> &depends) {
333+
const sample_fn_ptr_t fn =
334+
lookup_sampler(table, engine, double_precision);
335+
return py_sample(exec_q, seed, counter_lo, counter_hi, params, result,
336+
depends, fn);
337+
};
338+
339+
m.def(name, sample_pyapi, doc, py::arg("sycl_queue"), py::arg("engine"),
340+
py::arg("seed"), py::arg("counter_lo"), py::arg("counter_hi"),
341+
py::arg("params"), py::arg("result"), py::arg("double_precision"),
342+
py::arg("depends") = py::list());
343+
}
284344
} // namespace dpnp::extensions::rng
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
//*****************************************************************************
2+
// Copyright (c) 2026, Intel Corporation
3+
// All rights reserved.
4+
//
5+
// Redistribution and use in source and binary forms, with or without
6+
// modification, are permitted provided that the following conditions are met:
7+
// - Redistributions of source code must retain the above copyright notice,
8+
// this list of conditions and the following disclaimer.
9+
// - Redistributions in binary form must reproduce the above copyright notice,
10+
// this list of conditions and the following disclaimer in the documentation
11+
// and/or other materials provided with the distribution.
12+
// - Neither the name of the copyright holder nor the names of its contributors
13+
// may be used to endorse or promote products derived from this software
14+
// without specific prior written permission.
15+
//
16+
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17+
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18+
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19+
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20+
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21+
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22+
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23+
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24+
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25+
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
26+
// THE POSSIBILITY OF SUCH DAMAGE.
27+
//*****************************************************************************
28+
#include <pybind11/pybind11.h>
29+
30+
#include "common.hpp"
31+
#include "distributions.hpp"
32+
33+
namespace dpnp::extensions::rng
34+
{
35+
static discrete_dispatch_table_t geometric_dispatch_table;
36+
static discrete_dispatch_table_t logseries_dispatch_table;
37+
static discrete_dispatch_table_t zipf_dispatch_table;
38+
static discrete_dispatch_table_t hypergeometric_dispatch_table;
39+
40+
void init_discrete(py::module_ m)
41+
{
42+
def_discrete_sampler<rng_ns::GeometricSampler>(
43+
m, "_geometric",
44+
"Fill the result with geometric samples of probability p",
45+
geometric_dispatch_table);
46+
47+
def_discrete_sampler<rng_ns::LogseriesSampler>(
48+
m, "_logseries",
49+
"Fill the result with logarithmic series samples of shape p",
50+
logseries_dispatch_table);
51+
52+
def_discrete_sampler<rng_ns::ZipfSampler>(
53+
m, "_zipf", "Fill the result with Zipf samples of exponent a",
54+
zipf_dispatch_table);
55+
56+
def_discrete_sampler<rng_ns::HypergeometricSampler>(
57+
m, "_hypergeometric",
58+
"Fill the result with hypergeometric samples of nsample draws from "
59+
"ngood good and nbad bad items",
60+
hypergeometric_dispatch_table);
61+
}
62+
} // namespace dpnp::extensions::rng

‎dpnp/libtensor/extensions/rng/distributions.hpp‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,9 @@ void init_uniform(py::module_ m);
3838
void init_normal(py::module_ m);
3939
void init_exponential(py::module_ m);
4040
void init_gamma(py::module_ m);
41+
void init_noncentral(py::module_ m);
42+
void init_integers(py::module_ m);
43+
void init_poisson(py::module_ m);
44+
void init_binomial(py::module_ m);
45+
void init_discrete(py::module_ m);
4146
} // namespace dpnp::extensions::rng

0 commit comments

Comments
 (0)