Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .chplcheckignore
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ SipHash.chpl
SortMsg.chpl
SparseMatrix.chpl
SparseMatrixMsg.chpl
SplitMix64RNG.chpl
Stats.chpl
StatsMsg.chpl
StatusMsg.chpl
Expand Down
1 change: 1 addition & 0 deletions .configs/MultiDimTestServerModules.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ SequenceMsg
SetMsg
SortMsg
#SparseMatrixMsg
SplitMix64RNG
StatsMsg
#TimeClassMsg
TransferMsg
Expand Down
1 change: 1 addition & 0 deletions ServerModules.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ SequenceMsg
SetMsg
SortMsg
SparseMatrixMsg
SplitMix64RNG
StatsMsg
TimeClassMsg
TransferMsg
Expand Down
193 changes: 193 additions & 0 deletions arkouda/numpy/random/generator.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import numpy as np
import numpy.random as np_random

from typeguard import typechecked

from arkouda.client import get_registration_config
from arkouda.numpy.dtypes import (
_val_isinstance_of_union,
dtype_for_chapel,
float_scalars,
int_scalars,
numeric_scalars,
uint64,
)
from arkouda.numpy.dtypes import dtype as akdtype
from arkouda.numpy.dtypes import dtype as to_numpy_dtype
Expand Down Expand Up @@ -1058,6 +1061,196 @@ def uniform(self, low=0.0, high=1.0, size=None):
self._state += full_size
return create_pdarray(rep_msg)

@typechecked
def _fill_with_stateless_u64_from_index(
self, x: pdarray, seed: int_scalars, stream: int_scalars, start_idx: int_scalars = 0
) -> None:
"""
Fill an existing uint64 pdarray with stateless, index-based random values.

Random values are generated as a deterministic function of
``(seed, stream, global_index)`` and written in-place to ``x``.
No mutable RNG state is used, and results are invariant to
parallelism, chunking, and execution order.

Parameters
----------
x : pdarray
Destination array of dtype uint64 to be filled in-place.
Currently only 1-dimensional arrays are supported.
seed : int_scalars
Seed selecting the deterministic randomness universe. Converted
internally to uint64.
stream : int_scalars
Stream identifier used to generate independent random streams
from the same seed. Converted internally to uint64.
start_idx : int_scalars, optional
Global index offset corresponding to ``x[0]``. Converted internally
to uint64. Default is 0.

Notes
-----
This method is a low-level primitive intended for internal use.
It dispatches to a server-side kernel that generates values
independently for each global index.
"""
from arkouda.client import generic_msg

if x.ndim != 1:
raise ValueError("Only 1 dimensional arrays supported.")

# Normalize all scalar parameters to uint64 at the API boundary
seed_u64 = uint64(seed)
stream_u64 = uint64(stream)
start_idx_u64 = uint64(start_idx)

generic_msg(
cmd="fillRandU64<uint64,1>",
args={
"A": x,
"seed": seed_u64,
"stream": stream_u64,
"startIdx": start_idx_u64,
},
)

@typechecked
def _stateless_u64_from_index(
self, n: int, seed: int_scalars, stream: int_scalars, start_idx: int_scalars = 0
) -> pdarray:
"""
Create a uint64 pdarray of stateless, index-based random values.

The returned array satisfies::

out[i] = f(seed, stream, start_idx + i)

where ``f`` is a deterministic mixing function. Results are
reproducible across runs and invariant to distributed execution.

Parameters
----------
n : int
Number of elements to generate.
seed : int_scalars
Seed selecting the deterministic randomness universe. Converted
internally to uint64.
stream : int_scalars
Stream identifier used to generate independent random streams
from the same seed. Converted internally to uint64.
start_idx : int_scalars, optional
Global index offset corresponding to the first element.
Converted internally to uint64. Default is 0.

Returns
-------
pdarray
A uint64 pdarray of length ``n`` containing stateless random values.

Notes
-----
This is a fundamental building block for sampling, shuffling,
sketching, and other randomized algorithms.
"""
from arkouda.numpy.pdarraycreation import zeros as ak_zeros

x = ak_zeros(n, dtype=uint64)
self._fill_with_stateless_u64_from_index(x, seed, stream, start_idx)
return x

@typechecked
def _fill_stateless_uniform_01_from_index(
self,
x: pdarray,
seed: int_scalars,
stream: int_scalars,
start_idx: int_scalars = 0,
) -> None:
"""
Fill an existing float64 pdarray with stateless uniform random values in [0, 1).

Each element is generated deterministically from
``(seed, stream, global_index)`` and mapped to a floating-point
value uniformly distributed on [0, 1).

Parameters
----------
x : pdarray
Destination array of dtype float64 to be filled in-place.
Currently only 1-dimensional arrays are supported.
seed : int_scalars
Seed selecting the deterministic randomness universe. Converted
internally to uint64.
stream : int_scalars
Stream identifier used to generate independent random streams
from the same seed. Converted internally to uint64.
start_idx : int_scalars, optional
Global index offset corresponding to ``x[0]``.
Converted internally to uint64. Default is 0.

Notes
-----
This method performs all computation server-side and guarantees
reproducibility independent of locale count or execution order.
"""
from arkouda.client import generic_msg

if x.ndim != 1:
raise ValueError("Only 1 dimensional arrays supported.")

seed_u64 = uint64(seed)
stream_u64 = uint64(stream)
start_idx_u64 = uint64(start_idx)

generic_msg(
cmd="fillUniform01<float64,1>", # <-- MUST be the float64-uniform kernel
args={"A": x, "seed": seed_u64, "stream": stream_u64, "startIdx": start_idx_u64},
)

@typechecked
def _stateless_uniform_01_from_index(
self, n: int, seed: int_scalars, stream: int_scalars, start_idx: int_scalars = 0
) -> pdarray:
"""
Create a float64 pdarray of stateless uniform random values in [0, 1).

Random values are a deterministic function of
``(seed, stream, start_idx + i)`` for each element ``i``.
Results are reproducible across runs and invariant to
distributed execution details.

Parameters
----------
n : int
Number of elements to generate.
seed : int_scalars
Seed selecting the deterministic randomness universe. Converted
internally to uint64.
stream : int_scalars
Stream identifier used to generate independent random streams
from the same seed. Converted internally to uint64.
start_idx : int_scalars, optional
Global index offset corresponding to the first element.
Converted internally to uint64. Default is 0.

Returns
-------
pdarray
A float64 pdarray of length ``n`` with values in the half-open
interval [0, 1).

Notes
-----
This method is equivalent to generating a stateless uint64 sequence
and mapping it to floating-point values, but performs the operation
efficiently in a single server-side pass.
"""
from arkouda.numpy.pdarraycreation import zeros as ak_zeros

x = ak_zeros(n, dtype=akfloat64)
self._fill_stateless_uniform_01_from_index(x, seed, stream, start_idx)
return x


_supported_chapel_types = frozenset(("int", "int(64)", "uint", "uint(64)", "real", "real(64)", "bool"))

Expand Down
1 change: 1 addition & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ testpaths =
tests/numpy/pdarray_creation_test.py
tests/numpy/pdarraymanipulation_tests.py
tests/numpy/random_test.py
tests/numpy/random_stateless_rng_test.py
tests/numpy/segarray_test.py
tests/numpy/setops_test.py
tests/numpy/sort_test.py
Expand Down
63 changes: 63 additions & 0 deletions src/SplitMix64RNG.chpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// SplitMix64: fast 64-bit mixer suitable for stateless RNG / hashing.
// Works by design with overflow modulo 2^64, so we use uint(64) everywhere.

module SplitMix64RNG {
// Constants from the canonical SplitMix64 reference.
private param GAMMA: uint(64) = 0x9E3779B97F4A7C15:uint(64);
private param C1: uint(64) = 0xBF58476D1CE4E5B9:uint(64);
private param C2: uint(64) = 0x94D049BB133111EB:uint(64);

// A single SplitMix64 mix step.
// Given an input x, returns a well-scrambled 64-bit value.
inline proc splitmix64(xIn: uint(64)): uint(64) {
var x = xIn + GAMMA;
x = (x ^ (x >> 30)) * C1;
x = (x ^ (x >> 27)) * C2;
x = x ^ (x >> 31);
return x;
}

// Derive a per-stream key from seed and stream id.
// This makes streams "independent enough" for analytics/sampling/shuffle keys.
inline proc streamKey(seed: uint(64), stream: uint(64)): uint(64) {
// Any deterministic combination is fine; we mix it to avoid structure.
return splitmix64(seed ^ (stream * 0xD2B74407B1CE6E93:uint(64)));
}

// Stateless uint64 RNG at a global index i:
// r[i] = splitmix64(i ^ key(seed, stream))
inline proc randU64At(i: uint(64), seed: uint(64), stream: uint(64) = 0:uint(64)): uint(64) {
const k = streamKey(seed, stream);
return splitmix64(i ^ k);
}

// Convert uint64 to a uniform float64 in [0,1) using top 53 bits (exact grid).
inline proc u64ToUniform01(r: uint(64)): real(64) {
const top53 = r >> 11; // keep 53 MSBs
return top53:real(64) * 0x1.0p-53; // 2^-53
}

// Fill an array with uint64 randomness based on *global* indices.
// startIdx is the global index of A.domain.low (for Arkouda you'd pass the locale's global offset).
@arkouda.registerCommand
proc fillRandU64(ref A: [?d] ?t, seed: uint, stream: uint = 0: uint, startIdx: uint = 0:uint)
where (t == uint(64)) && (d.rank == 1) {
const k = streamKey(seed, stream);
forall idx in d with (ref A){
// idx is the domain index type; cast to uint(64) for a stable global counter
const i = startIdx + idx:uint(64);
A[idx] = splitmix64(i ^ k);
}
}

// Fill an array with uniform float64 in [0,1).
@arkouda.registerCommand
proc fillUniform01(ref A: [?d] ?t, seed: uint, stream: uint = 0:uint, startIdx: uint = 0:uint) throws
where (t == real) && (d.rank == 1){
const k = streamKey(seed, stream);
forall idx in d with (ref A){
const i = startIdx + idx:uint(64);
A[idx] = u64ToUniform01(splitmix64(i ^ k));
}
}
}
Loading