8

The fact that NumPy now recommends that new code uses the defacult_rng() instance instead of numpy.random for new code has got me thinking about how it should be used to yield good results, both performance vice and statistically.

This first example is how I first wanted to write:

import numpy as np

class fancy_name():
  def __init__(self):
    self.rg = np.random.default_rng()
    self.gamma_shape = 1.0
    self.gamma_scale = 1.0

  def public_method(self, input):
    # Do intelligent stuff with input
    return self.rg.gamma(self.gamma_shape, slef.gamma_scale)

But I have also considered creating a new instance in every function call:

import numpy as np

class fancy_name():
  def __init__(self):
    self.gamma_shape = 1.0
    self.gamma_scale = 1.0

  def public_method(self, input):
    # Do intelligent stuff with input
    rg = np.random.default_rng()
    return rg.gamma(self.gamma_shape, slef.gamma_scale)

A third alternative would be to pass the rng as an argument in the function call. This way the same rng can be used in other parts of the code as well.

This is used in a simulation environment that is going to be called often to sample, for example, transition times.

I guess the question is if there are arguments for any of these three methods and if there exists some kind of praxis?

Also, any reference to more in-depth explanations of using these random number generators (except for the NumPy doc and Random Sampling article) is of great interest!

Marcus
  • 401
  • 6
  • 13
  • (As an aside don't name your class `object`. `object` is the built-in base class for all Python classes. Doing this won't break anything immediately but it's still a bad idea) – Iguananaut May 08 '20 at 09:38
  • @Iguananaut Offcourse, just a dumb placeholder. Thank you for pointing out, so it does not cause confusion. – Marcus May 08 '20 at 09:41
  • I don't think there's any one right answer to your question as it depends on the use case. But in my own code I might do something like this. Define your `__init__` like `def __init__(self, rng=None)` then in the method body something like `if rng is None: rng = np.default_rng(); self.rng = rng`. Now you have the option to pass in a different RNG instance but otherwise it will use a default. I don't think there's much point in creating one for each function call. – Iguananaut May 08 '20 at 09:42

1 Answers1

6

default_rng() isn't a singleton. It makes a new Generator backed by a new instance of the default BitGenerator class. Quoting the docs:

Construct a new Generator with the default BitGenerator (PCG64).

...

If seed is not a BitGenerator or a Generator, a new BitGenerator is instantiated. This function does not manage a default global instance.

This can also be tested empirically:

In [1]: import numpy

In [2]: numpy.random.default_rng() is numpy.random.default_rng()
Out[2]: False

This is expensive. You should usually call default_rng() once in your program and pass the generator to anything that needs it. (Yes, this is awkward.)

Community
  • 1
  • 1
user2357112
  • 260,549
  • 28
  • 431
  • 505