Skip to content
Merged
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
9 changes: 0 additions & 9 deletions doc/source/bit_generators/chacha.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,6 @@ Seeding and State
~ChaCha.seed
~ChaCha.state

Parallel generation
===================
.. autosummary::
:toctree: generated/

~ChaCha.advance
~ChaCha.jump
~ChaCha.jumped

Extending
=========
.. autosummary::
Expand Down
23 changes: 4 additions & 19 deletions randomgen/aes.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import numpy as np

from randomgen.common cimport *
from randomgen.entropy import random_entropy, seed_by_array


__all__ = ["AESCounter"]

Expand Down Expand Up @@ -226,26 +226,11 @@ cdef class AESCounter(BitGenerator):
raise ValueError("seed and key cannot be both used")
if key is None:
BitGenerator._seed_with_seed_sequence(self, seed, counter=counter)
try:
if self.seed_seq is not None:
return
except AttributeError:
if self._seed_seq is not None:
return

seed = object_to_int(seed, 128, "seed")
return

key = object_to_int(key, 128, "key")
counter = object_to_int(counter, 128, "counter")
if seed is not None and key is not None:
raise ValueError("seed and key cannot be both used")
if key is None:
if seed is None:
_seed = random_entropy(4, "auto")
_seed = _seed.view(np.uint64)
else:
_seed = seed_by_array(int_to_array(seed, "seed", None, 64), 2)
else:
_seed = int_to_array(key, "key", 128, 64)
_seed = int_to_array(key, "key", 128, 64)
# TODO: We have swapped here, but should we always use native in Python?
aesctr_seed(self.rng_state, <uint64_t*>np.PyArray_DATA(_seed))
_counter = np.empty(8, dtype=np.uint64)
Expand Down
1 change: 0 additions & 1 deletion randomgen/chacha.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,3 @@ cdef extern from "src/chacha/chacha.h":
cdef class ChaCha(BitGenerator):

cdef chacha_state_t *rng_state
cdef jump_inplace(self, object iter)
3 changes: 0 additions & 3 deletions randomgen/chacha.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,3 @@ class ChaCha(BitGenerator):
def state(self) -> dict[str, str | dict[str, ndarray | int]]: ...
@state.setter
def state(self, value: dict[str, str | dict[str, ndarray | int]]) -> None: ...
def jump(self, iter: int = ...) -> ChaCha: ...
def jumped(self, iter: int = ...) -> ChaCha: ...
def advance(self, delta: int) -> ChaCha: ...
131 changes: 4 additions & 127 deletions randomgen/chacha.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import numpy as np

from randomgen.common cimport *
from randomgen.entropy import random_entropy, seed_by_array

__all__ = ["ChaCha"]

Expand Down Expand Up @@ -222,27 +221,14 @@ cdef class ChaCha(BitGenerator):
array[i] = (value // 2**(64*i)) % 2**64.
"""
if seed is not None and key is not None:
raise ValueError("seed and key cannot be both used")
raise ValueError("seed and key cannot be simultaneously used")
if key is None:
BitGenerator._seed_with_seed_sequence(self, seed, counter=counter)
try:
if self.seed_seq is not None:
return
except AttributeError:
if self._seed_seq is not None:
return

seed = object_to_int(seed, 256, "seed")
return

key = object_to_int(key, 256, "key")
counter = object_to_int(counter, 128, "counter")
if seed is not None and key is not None:
raise ValueError("seed and key cannot be simultaneously used")
if key is not None:
seed = int_to_array(key, "key", 256, 32)
elif seed is not None:
seed = seed_by_array(int_to_array(seed, "seed", None, 64), 4)
else:
seed = random_entropy(8, "auto")
seed = int_to_array(key, "key", 256, 32)
_seed = seed
if _seed.dtype != np.uint64:
_seed = view_little_endian(_seed, np.uint64)
Expand Down Expand Up @@ -301,112 +287,3 @@ cdef class ChaCha(BitGenerator):
for i in range(2):
self.rng_state.ctr[i] = ctr[i]
self.rng_state.rounds = state["rounds"]

cdef jump_inplace(self, object iter):
"""
Jump state in-place

Not part of public API

Parameters
----------
iter : integer, positive
Number of times to jump the state of the rng.
"""
self.advance(iter * int(2 ** 64))

def jump(self, iter=1):
"""
jump(iter=1)

Jumps the state as-if 2**64 random numbers have been generated.

Parameters
----------
iter : integer, positive
Number of times to jump the state of the rng.

Returns
-------
self : ChaCha
PRNG jumped iter times

Notes
-----
Jumping the rng state resets any pre-computed random numbers. This is
required to ensure exact reproducibility.
"""
import warnings
warnings.warn("jump (in-place) has been deprecated in favor of jumped"
", which returns a new instance", DeprecationWarning)
self.jump_inplace(iter)
return self

def jumped(self, iter=1):
"""
jumped(iter=1)

Returns a new bit generator with the state jumped

The state of the returned big generator is jumped as-if
2**(64 * iter) random numbers have been generated.

Parameters
----------
iter : integer, positive
Number of times to jump the state of the bit generator returned

Returns
-------
bit_generator : ChaCha
New instance of generator jumped iter times
"""
cdef ChaCha bit_generator

bit_generator = self.__class__(seed=self._copy_seed())
bit_generator.state = self.state
bit_generator.jump_inplace(iter)

return bit_generator

def advance(self, delta):
"""
advance(delta)

Advance the underlying RNG as-if delta draws have occurred.

Parameters
----------
delta : integer, positive
Number of draws to advance the RNG.

Returns
-------
self : ChaCha
RNG advanced delta steps

Notes
-----
Advancing a RNG updates the underlying RNG state as-if a given
number of calls to the underlying RNG have been made. In general
there is not a one-to-one relationship between the number output
random values from a particular distribution and the number of
draws from the core RNG. This occurs for two reasons:

* The random values are simulated using a rejection-based method
and so, on average, more than one value from the underlying
RNG is required to generate an single draw.
* The number of bits required to generate a simulated value
differs from the number of bits generated by the underlying
RNG. For example, two 16-bit integer values can be simulated
from a single draw of a 32-bit RNG.

Advancing the RNG state resets any pre-computed random numbers.
This is required to ensure exact reproducibility.
"""
cdef np.ndarray step

delta = wrap_int(delta, 128)
step = int_to_array(delta, "delta", 128, 64)
chacha_advance(self.rng_state, <uint64_t *>np.PyArray_DATA(step))
return self
18 changes: 6 additions & 12 deletions randomgen/common.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -68,21 +68,15 @@ cdef class BitGenerator(_BitGenerator):
return copy.deepcopy(self._seed_seq)

def _seed_with_seed_sequence(self, seed, **kwargs):
from randomgen.seed_sequence import SeedSequence
DefaultSeedSequence = SeedSequence
try:
from numpy.random import SeedSequence as DefaultSeedSequence
except ImportError:
pass
from numpy.random import SeedSequence
if isinstance(seed, ISEED_SEQUENCES):
self._seed_seq = seed
else:
self._seed_seq = DefaultSeedSequence(seed)
if self._seed_seq is not None:
if self.mode == "sequence":
self._seed_from_seq(**kwargs)
else: # numpy
self._seed_from_seq_numpy_compat(**kwargs)
self._seed_seq = SeedSequence(seed)
if self.mode == "sequence":
self._seed_from_seq(**kwargs)
else: # numpy
self._seed_from_seq_numpy_compat(**kwargs)
return

@property
Expand Down
33 changes: 0 additions & 33 deletions randomgen/dsfmt.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import numpy as np
cimport numpy as np

from randomgen.common cimport *
from randomgen.entropy import random_entropy

__all__ = ["DSFMT"]

Expand Down Expand Up @@ -168,39 +167,7 @@ cdef class DSFMT(BitGenerator):
ValueError
If seed values are out of range for the PRNG.
"""
cdef np.ndarray obj, seed_arr

BitGenerator._seed_with_seed_sequence(self, seed)
try:
if self.seed_seq is not None:
return
except AttributeError:
if self._seed_seq is not None:
return
try:
if seed is None:
seed_arr = random_entropy(2 * DSFMT_N64, "auto")
dsfmt_init_by_array(self.rng_state.state,
<uint32_t *>np.PyArray_DATA(seed_arr),
2 * DSFMT_N64)

else:
if hasattr(seed, "squeeze"):
seed = seed.squeeze()
idx = operator.index(seed)
if idx > int(2**32 - 1) or idx < 0:
raise ValueError("Seed must be between 0 and 2**32 - 1")
dsfmt_init_gen_rand(self.rng_state.state, seed)
except TypeError:
obj = np.asarray(seed).astype(np.int64, casting="safe").ravel()
if ((obj > int(2**32 - 1)) | (obj < 0)).any():
raise ValueError("Seed must be between 0 and 2**32 - 1")
seed_arr = obj.astype(np.uint32, casting="unsafe", order="C")
dsfmt_init_by_array(self.rng_state.state,
<uint32_t *>np.PyArray_DATA(seed_arr),
<int>np.PyArray_DIM(seed_arr, 0))
# Clear the buffer
self._reset_state_variables()

cdef jump_inplace(self, object iter):
"""
Expand Down
21 changes: 4 additions & 17 deletions randomgen/hc128.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import numpy as np
cimport numpy as np

from randomgen.common cimport *
from randomgen.entropy import random_entropy, seed_by_array

__all__ = ["HC128"]

Expand Down Expand Up @@ -156,23 +155,11 @@ cdef class HC128(BitGenerator):
raise ValueError("seed and key cannot be simultaneously used")
if key is None:
BitGenerator._seed_with_seed_sequence(self, seed)
try:
if self.seed_seq is not None:
return
except AttributeError:
if self._seed_seq is not None:
return

seed = object_to_int(seed, 256, "seed")
return

key = object_to_int(key, 256, "key")
if key is not None:
state = int_to_array(key, "key", 256, 64)
elif seed is not None:
state = seed_by_array(int_to_array(seed, "seed", None, 64), 4)
else:
state = random_entropy(8, "auto")
# Ensure state uint32 values are the same in LE and BE
# and in the same order
state = int_to_array(key, "key", 256, 64)
# Ensure state uint32 values are the same in LE and BE and in the same order
state = view_little_endian(state, np.uint32)
hc128_seed(&self.rng_state, <uint32_t *>np.PyArray_DATA(state))

Expand Down
21 changes: 0 additions & 21 deletions randomgen/jsf.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import numpy as np
cimport numpy as np

from randomgen.common cimport *
from randomgen.entropy import random_entropy, seed_by_array

__all__ = ["JSF"]

Expand Down Expand Up @@ -266,26 +265,6 @@ cdef class JSF(BitGenerator):
If seed values are out of range for the PRNG.
"""
BitGenerator._seed_with_seed_sequence(self, seed)
try:
if self.seed_seq is not None:
return
except AttributeError:
if self._seed_seq is not None:
return

if seed is None:
state = random_entropy(3 * self.size // 32, "auto")
else:
state = seed_by_array(seed, 3)
dtype = np.uint64 if self.size==64 else np.uint32
state = view_little_endian(state, dtype)
if self.size == 64:
jsf64_seed(&self.rng_state, <uint64_t*>np.PyArray_DATA(state),
self.seed_size)
else:
jsf32_seed(&self.rng_state, <uint32_t*>np.PyArray_DATA(state),
self.seed_size)
self._reset_state_variables()

@property
def state(self):
Expand Down
Loading
Loading