Skip to content

Commit 9757f8a

Browse files
committed
ENH: add support for encoding/decoding numpy random generators
1 parent a0b77f2 commit 9757f8a

1 file changed

Lines changed: 56 additions & 0 deletions

File tree

bilby/core/utils/io.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ def default(self, obj):
6464
return {"__dataframe__": True, "content": obj.to_dict(orient="list")}
6565
if isinstance(obj, pd.Series):
6666
return {"__series__": True, "content": obj.to_dict()}
67+
if isinstance(obj, np.random.Generator):
68+
return encode_numpy_random_generator(obj)
6769
if inspect.isfunction(obj):
6870
return {
6971
"__function__": True,
@@ -133,6 +135,30 @@ def encode_astropy_unit(obj):
133135
return obj
134136

135137

138+
def encode_numpy_random_generator(generator):
139+
"""Encode a numpy random generator to a dictionary.
140+
141+
Adds the key :code:`__numpy_random_generator__` to the dictionary to indicate
142+
that the object is a numpy random generator.
143+
144+
The :code:`bit_generator_state` key contains the state of the bit generator
145+
including its name.
146+
147+
.. versionadded:: 2.5.0
148+
"""
149+
state = generator.bit_generator.state
150+
# The state and inc integers are converted to strings to avoid issues with
151+
# arbitrary precision integers when encoding to HDF5.
152+
# We list them explicitly to avoid issues that may arise if the numpy
153+
# random generator changes its state representation.
154+
state["state"]["state"] = str(state["state"]["state"])
155+
state["state"]["inc"] = str(state["state"]["inc"])
156+
return {
157+
"__numpy_random_generator__": True,
158+
"bit_generator_state": state,
159+
}
160+
161+
136162
def decode_astropy_cosmology(dct):
137163
"""Decode an astropy cosmology from a dictionary.
138164
@@ -217,6 +243,30 @@ def decode_astropy_unit(dct):
217243
return dct
218244

219245

246+
def decode_numpy_random_generator(dct):
247+
"""Decode a numpy random generator from a dictionary.
248+
249+
.. versionadded:: 2.5.0
250+
"""
251+
try:
252+
bit_generator = getattr(np.random, dct["bit_generator_state"]["bit_generator"])
253+
except AttributeError as e:
254+
raise ValueError(
255+
f"Unknown numpy bit generator {dct['bit_generator_state']['bit_generator']}. "
256+
"The bit generator must be a numpy.random.Generator."
257+
f"Original error: {e}"
258+
) from e
259+
# Convert the state and inc integers back to integers
260+
dct["bit_generator_state"]["state"]["state"] = \
261+
int(dct["bit_generator_state"]["state"]["state"])
262+
dct["bit_generator_state"]["state"]["inc"] = \
263+
int(dct["bit_generator_state"]["state"]["inc"])
264+
265+
generator = np.random.Generator(bit_generator())
266+
generator.bit_generator.state = dct["bit_generator_state"]
267+
return generator
268+
269+
220270
def load_json(filename, gzip):
221271
if gzip or os.path.splitext(filename)[1].lstrip(".") == "gz":
222272
import gzip
@@ -252,6 +302,8 @@ def decode_bilby_json(dct):
252302
cls = Prior
253303
obj = cls(**dct["kwargs"])
254304
return obj
305+
if dct.get("__numpy_random_generator__", False):
306+
return decode_numpy_random_generator(dct)
255307
if dct.get("__cosmology__", False):
256308
return decode_astropy_cosmology(dct)
257309
if dct.get("__astropy_quantity__", False):
@@ -372,6 +424,8 @@ def encode_for_hdf5(key, item):
372424
item = np.array(item, dtype='S')
373425
if isinstance(item, (np.ndarray, int, float, complex, str, bytes)):
374426
output = item
427+
elif isinstance(item, np.random.Generator):
428+
output = encode_numpy_random_generator(item)
375429
elif item is None:
376430
output = "__none__"
377431
elif isinstance(item, list):
@@ -434,6 +488,8 @@ def decode_hdf5_dict(output):
434488
output = decode_astropy_quantity(output)
435489
elif "__astropy_unit__" in output:
436490
output = decode_astropy_unit(output)
491+
elif "__numpy_random_generator__" in output:
492+
output = decode_numpy_random_generator(output)
437493
return output
438494

439495

0 commit comments

Comments
 (0)