| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,25 @@ | |||
| 1 | + The bit generator underlying the singleton RandomState can be changed | ||
| 2 | + --------------------------------------------------------------------- | ||
| 3 | + The singleton ``RandomState`` instance exposed in the ``numpy.random`` module | ||
| 4 | + id initialized using system-provided entropy with the ``MT19937` bit generator. | ||
| 5 | + The function ``set_bit_generator`` allows the default bit generator to be | ||
| 6 | + replaced with a user-provided bit generator. This function has been introduced to | ||
| 7 | + provide a method allowing seemless integration of a high-quality, modern bit generator | ||
| 8 | + in new code with existing code that makes use of the singleton-provided random | ||
| 9 | + variate generating functions. | ||
| 10 | + | ||
| 11 | + The preferred method to generate reproducible random numbers is to use a modern | ||
| 12 | + bit generator in an instance of ``Generator``. The function ``default_rng`` | ||
| 13 | + simplifies instantization. | ||
| 14 | + | ||
| 15 | + >>> rg = np.random.default_rng(3728973198) | ||
| 16 | + >>> rg.random() | ||
| 17 | + | ||
| 18 | + The same bit generator can then shared with the singleton instance so that | ||
| 19 | + calling functions in the ``random`` module will use the same bit generator. | ||
| 20 | + | ||
| 21 | + >>> np.random.set_bit_generator(rg.bit_generator) | ||
| 22 | + >>> np.random.normal() | ||
| 23 | + | ||
| 24 | + The swap is permanent (until reversed) and so any call to functions | ||
| 25 | + in the ``random`` module will use the new bit generator. | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -186,16 +186,7 @@ cdef class RandomState: | |||
| 186 | 186 | else: | |
| 187 | 187 | bit_generator = seed | |
| 188 | 188 | ||
| 189 | - self._bit_generator = bit_generator | ||
| 190 | - capsule = bit_generator.capsule | ||
| 191 | - cdef const char *name = "BitGenerator" | ||
| 192 | - if not PyCapsule_IsValid(capsule, name): | ||
| 193 | - raise ValueError("Invalid bit generator. The bit generator must " | ||
| 194 | - "be instantized.") | ||
| 195 | - self._bitgen = (<bitgen_t *> PyCapsule_GetPointer(capsule, name))[0] | ||
| 196 | - self._aug_state.bit_generator = &self._bitgen | ||
| 197 | - self._reset_gauss() | ||
| 198 | - self.lock = bit_generator.lock | ||
| 189 | + self._initialize_bit_generator(bit_generator) | ||
| 199 | 190 | ||
| 200 | 191 | def __repr__(self): | |
| 201 | 192 | return self.__str__() + ' at 0x{:X}'.format(id(self)) | |
@@ -217,13 +208,25 @@ cdef class RandomState: | |||
| 217 | 208 | from ._pickle import __randomstate_ctor | |
| 218 | 209 | return __randomstate_ctor, (state['bit_generator'],), state | |
| 219 | 210 | ||
| 211 | + cdef _initialize_bit_generator(self, bit_generator): | ||
| 212 | + self._bit_generator = bit_generator | ||
| 213 | + capsule = bit_generator.capsule | ||
| 214 | + cdef const char *name = "BitGenerator" | ||
| 215 | + if not PyCapsule_IsValid(capsule, name): | ||
| 216 | + raise ValueError("Invalid bit generator. The bit generator must " | ||
| 217 | + "be instantized.") | ||
| 218 | + self._bitgen = (<bitgen_t *> PyCapsule_GetPointer(capsule, name))[0] | ||
| 219 | + self._aug_state.bit_generator = &self._bitgen | ||
| 220 | + self._reset_gauss() | ||
| 221 | + self.lock = bit_generator.lock | ||
| 222 | + | ||
| 220 | 223 | cdef _reset_gauss(self): | |
| 221 | 224 | self._aug_state.has_gauss = 0 | |
| 222 | 225 | self._aug_state.gauss = 0.0 | |
| 223 | 226 | ||
| 224 | 227 | def seed(self, seed=None): | |
| 225 | 228 | """ | |
| 226 | - seed(self, seed=None) | ||
| 229 | + seed(seed=None) | ||
| 227 | 230 | ||
| 228 | 231 | Reseed a legacy MT19937 BitGenerator | |
| 229 | 232 | ||
@@ -248,7 +251,7 @@ cdef class RandomState: | |||
| 248 | 251 | ||
| 249 | 252 | def get_state(self, legacy=True): | |
| 250 | 253 | """ | |
| 251 | - get_state() | ||
| 254 | + get_state(legacy=True) | ||
| 252 | 255 | ||
| 253 | 256 | Return a tuple representing the internal state of the generator. | |
| 254 | 257 | ||
@@ -258,12 +261,13 @@ cdef class RandomState: | |||
| 258 | 261 | ---------- | |
| 259 | 262 | legacy : bool, optional | |
| 260 | 263 | Flag indicating to return a legacy tuple state when the BitGenerator | |
| 261 | - is MT19937, instead of a dict. | ||
| 264 | + is MT19937, instead of a dict. Raises ValueError if the underlying | ||
| 265 | + bit generator is not an instance of MT19937. | ||
| 262 | 266 | ||
| 263 | 267 | Returns | |
| 264 | 268 | ------- | |
| 265 | 269 | out : {tuple(str, ndarray of 624 uints, int, int, float), dict} | |
| 266 | - The returned tuple has the following items: | ||
| 270 | + If legacy is True, the returned tuple has the following items: | ||
| 267 | 271 | ||
| 268 | 272 | 1. the string 'MT19937'. | |
| 269 | 273 | 2. a 1-D array of 624 unsigned integer keys. | |
@@ -293,6 +297,11 @@ cdef class RandomState: | |||
| 293 | 297 | legacy = False | |
| 294 | 298 | st['has_gauss'] = self._aug_state.has_gauss | |
| 295 | 299 | st['gauss'] = self._aug_state.gauss | |
| 300 | + if legacy and not isinstance(self._bit_generator, _MT19937): | ||
| 301 | + raise ValueError( | ||
| 302 | + "legacy can only be True when the underlyign bitgenerator is " | ||
| 303 | + "an instance of MT19937." | ||
| 304 | + ) | ||
| 296 | 305 | if legacy: | |
| 297 | 306 | return (st['bit_generator'], st['state']['key'], st['state']['pos'], | |
| 298 | 307 | st['has_gauss'], st['gauss']) | |
@@ -4689,7 +4698,6 @@ random = _rand.random | |||
| 4689 | 4698 | random_integers = _rand.random_integers | |
| 4690 | 4699 | random_sample = _rand.random_sample | |
| 4691 | 4700 | rayleigh = _rand.rayleigh | |
| 4692 | - seed = _rand.seed | ||
| 4693 | 4701 | set_state = _rand.set_state | |
| 4694 | 4702 | shuffle = _rand.shuffle | |
| 4695 | 4703 | standard_cauchy = _rand.standard_cauchy | |
@@ -4704,6 +4712,85 @@ wald = _rand.wald | |||
| 4704 | 4712 | weibull = _rand.weibull | |
| 4705 | 4713 | zipf = _rand.zipf | |
| 4706 | 4714 | ||
| 4715 | + def seed(seed=None): | ||
| 4716 | + """ | ||
| 4717 | + seed(seed=None) | ||
| 4718 | + | ||
| 4719 | + Reseed the singleton RandomState instance. | ||
| 4720 | + | ||
| 4721 | + Notes | ||
| 4722 | + ----- | ||
| 4723 | + This is a convenience, legacy function that exists to support | ||
| 4724 | + older code that uses the singleton RandomState. Best practice | ||
| 4725 | + is to use a dedicated ``Generator`` instance rather than | ||
| 4726 | + the random variate generation methods exposed directly in | ||
| 4727 | + the random module. | ||
| 4728 | + | ||
| 4729 | + See Also | ||
| 4730 | + -------- | ||
| 4731 | + numpy.random.Generator | ||
| 4732 | + """ | ||
| 4733 | + if isinstance(_rand._bit_generator, _MT19937): | ||
| 4734 | + return _rand.seed(seed) | ||
| 4735 | + else: | ||
| 4736 | + bg_type = type(_rand._bit_generator) | ||
| 4737 | + _rand._bit_generator.state = bg_type(seed).state | ||
| 4738 | + | ||
| 4739 | + def get_bit_generator(): | ||
| 4740 | + """ | ||
| 4741 | + Returns the singleton RandomState's bit generator | ||
| 4742 | + | ||
| 4743 | + Returns | ||
| 4744 | + ------- | ||
| 4745 | + BitGenerator | ||
| 4746 | + The bit generator that underlies the singleton RandomState instance | ||
| 4747 | + | ||
| 4748 | + Notes | ||
| 4749 | + ----- | ||
| 4750 | + The singleton RandomState provides the random variate generators in the | ||
| 4751 | + NumPy random namespace. This function, and its counterpart set method, | ||
| 4752 | + provides a path to hot-swap the default MT19937 bit generator with a | ||
| 4753 | + user provided alternative. These function are intended to provide | ||
| 4754 | + a continuous path where a single underlying bit generator can be | ||
| 4755 | + used both with an instance of ``Generator`` and with the singleton | ||
| 4756 | + instance of RandomState. | ||
| 4757 | + | ||
| 4758 | + See Also | ||
| 4759 | + -------- | ||
| 4760 | + set_bit_generator | ||
| 4761 | + numpy.random.Generator | ||
| 4762 | + """ | ||
| 4763 | + return _rand._bit_generator | ||
| 4764 | + | ||
| 4765 | + def set_bit_generator(bitgen): | ||
| 4766 | + """ | ||
| 4767 | + Sets the singleton RandomState's bit generator | ||
| 4768 | + | ||
| 4769 | + Parameters | ||
| 4770 | + ---------- | ||
| 4771 | + bitgen | ||
| 4772 | + A bit generator instance | ||
| 4773 | + | ||
| 4774 | + Notes | ||
| 4775 | + ----- | ||
| 4776 | + The singleton RandomState provides the random variate generators in the | ||
| 4777 | + NumPy random namespace. This function, and its counterpart get method, | ||
| 4778 | + provides a path to hot-swap the default MT19937 bit generator with a | ||
| 4779 | + user provided alternative. These function are intended to provide | ||
| 4780 | + a continuous path where a single underlying bit generator can be | ||
| 4781 | + used both with an instance of ``Generator`` and with the singleton | ||
| 4782 | + instance of RandomState. | ||
| 4783 | + | ||
| 4784 | + See Also | ||
| 4785 | + -------- | ||
| 4786 | + get_bit_generator | ||
| 4787 | + numpy.random.Generator | ||
| 4788 | + """ | ||
| 4789 | + cdef RandomState singleton | ||
| 4790 | + singleton = _rand | ||
| 4791 | + singleton._initialize_bit_generator(bitgen) | ||
| 4792 | + | ||
| 4793 | + | ||
| 4707 | 4794 | # Old aliases that should not be removed | |
| 4708 | 4795 | def sample(*args, **kwargs): | |
| 4709 | 4796 | """ | |
@@ -4730,6 +4817,7 @@ __all__ = [ | |||
| 4730 | 4817 | 'f', | |
| 4731 | 4818 | 'gamma', | |
| 4732 | 4819 | 'geometric', | |
| 4820 | + 'get_bit_generator', | ||
| 4733 | 4821 | 'get_state', | |
| 4734 | 4822 | 'gumbel', | |
| 4735 | 4823 | 'hypergeometric', | |
@@ -4757,6 +4845,7 @@ __all__ = [ | |||
| 4757 | 4845 | 'rayleigh', | |
| 4758 | 4846 | 'sample', | |
| 4759 | 4847 | 'seed', | |
| 4848 | + 'set_bit_generator', | ||
| 4760 | 4849 | 'set_state', | |
| 4761 | 4850 | 'shuffle', | |
| 4762 | 4851 | 'standard_cauchy', | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -53,6 +53,14 @@ def int_func(request): | |||
| 53 | 53 | INT_FUNC_HASHES[request.param]) | |
| 54 | 54 | ||
| 55 | 55 | ||
| 56 | + @pytest.fixture | ||
| 57 | + def restore_singleton_bitgen(): | ||
| 58 | + """Ensures that the singleton bitgen is restored after a test""" | ||
| 59 | + orig_bitgen = np.random.get_bit_generator() | ||
| 60 | + yield | ||
| 61 | + np.random.set_bit_generator(orig_bitgen) | ||
| 62 | + | ||
| 63 | + | ||
| 56 | 64 | def assert_mt19937_state_equal(a, b): | |
| 57 | 65 | assert_equal(a['bit_generator'], b['bit_generator']) | |
| 58 | 66 | assert_array_equal(a['state']['key'], b['state']['key']) | |
@@ -2020,3 +2028,63 @@ def test_broadcast_size_error(): | |||
| 2020 | 2028 | random.binomial([1, 2], 0.3, size=(2, 1)) | |
| 2021 | 2029 | with pytest.raises(ValueError): | |
| 2022 | 2030 | random.binomial([1, 2], [0.3, 0.7], size=(2, 1)) | |
| 2031 | + | ||
| 2032 | + | ||
| 2033 | + def test_hot_swap(restore_singleton_bitgen): | ||
| 2034 | + # GH 21808 | ||
| 2035 | + def_bg = np.random.default_rng(0) | ||
| 2036 | + bg = def_bg.bit_generator | ||
| 2037 | + np.random.set_bit_generator(bg) | ||
| 2038 | + assert isinstance(np.random.mtrand._rand._bit_generator, type(bg)) | ||
| 2039 | + | ||
| 2040 | + second_bg = np.random.get_bit_generator() | ||
| 2041 | + assert bg is second_bg | ||
| 2042 | + | ||
| 2043 | + | ||
| 2044 | + def test_seed_alt_bit_gen(restore_singleton_bitgen): | ||
| 2045 | + # GH 21808 | ||
| 2046 | + bg = PCG64(0) | ||
| 2047 | + np.random.set_bit_generator(bg) | ||
| 2048 | + state = np.random.get_state(legacy=False) | ||
| 2049 | + np.random.seed(1) | ||
| 2050 | + new_state = np.random.get_state(legacy=False) | ||
| 2051 | + print(state) | ||
| 2052 | + print(new_state) | ||
| 2053 | + assert state["bit_generator"] == "PCG64" | ||
| 2054 | + assert state["state"]["state"] != new_state["state"]["state"] | ||
| 2055 | + assert state["state"]["inc"] != new_state["state"]["inc"] | ||
| 2056 | + | ||
| 2057 | + | ||
| 2058 | + def test_state_error_alt_bit_gen(restore_singleton_bitgen): | ||
| 2059 | + # GH 21808 | ||
| 2060 | + state = np.random.get_state() | ||
| 2061 | + bg = PCG64(0) | ||
| 2062 | + np.random.set_bit_generator(bg) | ||
| 2063 | + with pytest.raises(ValueError, match="state must be for a PCG64"): | ||
| 2064 | + np.random.set_state(state) | ||
| 2065 | + | ||
| 2066 | + | ||
| 2067 | + def test_swap_worked(restore_singleton_bitgen): | ||
| 2068 | + # GH 21808 | ||
| 2069 | + np.random.seed(98765) | ||
| 2070 | + vals = np.random.randint(0, 2 ** 30, 10) | ||
| 2071 | + bg = PCG64(0) | ||
| 2072 | + state = bg.state | ||
| 2073 | + np.random.set_bit_generator(bg) | ||
| 2074 | + state_direct = np.random.get_state(legacy=False) | ||
| 2075 | + for field in state: | ||
| 2076 | + assert state[field] == state_direct[field] | ||
| 2077 | + np.random.seed(98765) | ||
| 2078 | + pcg_vals = np.random.randint(0, 2 ** 30, 10) | ||
| 2079 | + assert not np.all(vals == pcg_vals) | ||
| 2080 | + new_state = bg.state | ||
| 2081 | + assert new_state["state"]["state"] != state["state"]["state"] | ||
| 2082 | + assert new_state["state"]["inc"] == new_state["state"]["inc"] | ||
| 2083 | + | ||
| 2084 | + | ||
| 2085 | + def test_swapped_singleton_against_direct(restore_singleton_bitgen): | ||
| 2086 | + np.random.set_bit_generator(PCG64(98765)) | ||
| 2087 | + singleton_vals = np.random.randint(0, 2 ** 30, 10) | ||
| 2088 | + rg = np.random.RandomState(PCG64(98765)) | ||
| 2089 | + non_singleton_vals = rg.randint(0, 2 ** 30, 10) | ||
| 2090 | + assert_equal(non_singleton_vals, singleton_vals) | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments