Anyone who has converted code from one language to another, where there is a random number generator involved, knows the pain of rigorously checking that both versions of code do the exact same thing. In the past I’ve done a write out to file from one of the language’s random number generators (RNGs), and then read in from file, but it’s still be a pain in the ass as there’s some overhead involved and you have to change everything back and forth if you need to do debugging / comparison in the future, etc etc. After some searching, it doesn’t seem that there are any common RNGs, and the closest I found was a suggestion saying to write the same algorithm out in each of the languages and use that.
Well. I happen to know how to use Cython now, and rather than rewrite the same code in C++ and Python, I thought it was a perfect opportunity to put to use this knowledge. There’s a program called SimpleRNG for C++ (http://www.codeproject.com/Articles/25172/Simple-Random-Number-Generation), with a whole slew of basic random number generation methods, so the idea was just to take that and throw some Cython at it to get access to the same RNG in both C++ and Python. It turned out to be almost a trivial exercise, but with very useful results!
Since we already have the SimpleRNG.h and SimpleRNG.cpp code all written, taken from link above, we can start by making a .pyx
file that will 1) provide a Cython handle to the C++ code, and 2) define a Python class that can call these functions. Remembering not to name the .pyx
file the same thing as the .cpp
files, for fear of the code generated by Cython overwriting my .cpp
files, I added a “py” prefix. The first part of the .pyx
file is just redefining all the functionality we want from the header file:
pySimpleRNG.pyx
cdef extern from "SimpleRNG.h": cdef cppclass SimpleRNG: SimpleRNG() # Seed the random number generator void SetState(unsigned int u, unsigned int v) # A uniform random sample from the open interval (0, 1) double GetUniform() # A uniform random sample from the set of unsigned integers unsigned int GetUint() # Normal (Gaussian) random sample double GetNormal(double mean, double standardDeviation) # Exponential random sample double GetExponential(double mean) # Gamma random sample double GetGamma(double shape, double scale) # Chi-square sample double GetChiSquare(double degreesOfFreedom) # Inverse-gamma sample double GetInverseGamma(double shape, double scale) # Weibull sample double GetWeibull(double shape, double scale) # Cauchy sample double GetCauchy(double median, double scale) # Student-t sample double GetStudentT(double degreesOfFreedom) # The Laplace distribution is also known as the double exponential distribution. double GetLaplace(double mean, double scale) # Log-normal sample double GetLogNormal(double mu, double sigma) # Beta sample double GetBeta(double a, double b) # Poisson sample int GetPoisson(double lam)
Look at all those functions! I left out two functions from the redefinition, namely double GetUniform(unsigned int& u, unsigned int& v)
and unsigned int GetUint(unsigned int& u, unsigned int& v)
, for the simple reason that I don’t want to deal with reference operators in Cython, and I don’t have any need for the functionality provided with the overloaded GetUniform()
and GetUint()
functions.
Alright, the first part is done. Second part! Straightforward again, define a Python class, create a pointer to cppclass
we just defined, and then make a bunch of handle functions to call them up. That looks like:
pySimpleRNG.pyx
cdef class pySimpleRNG: cdef SimpleRNG* thisptr # hold a C++ instance def __cinit__(self): self.thisptr = new SimpleRNG() def __dealloc__(self): del self.thisptr # Seed the random number generator def SetState(self, unsigned int u, unsigned int v): self.thisptr.SetState(u, v) # A uniform random sample from the open interval (0, 1) def GetUniform(self): return self.thisptr.GetUniform() # A uniform random sample from the set of unsigned integers def GetUint(self): return self.thisptr.GetUint() # Normal (Gaussian) random sample def GetNormal(self, double mean=0, double std_dev=1): return self.thisptr.GetNormal(mean, std_dev) # Exponential random sample def GetExponential(self, double mean): return self.thisptr.GetExponential(mean) # Gamma random sample def GetGamma(self, double shape, double scale): return self.thisptr.GetGamma(shape, scale) # Chi-square sample def GetChiSquare(self, double degreesOfFreedom): return self.thisptr.GetChiSquare(degreesOfFreedom) # Inverse-gamma sample def GetInverseGamma(self, double shape, double scale): return self.thisptr.GetInverseGamma(shape, scale) # Weibull sample def GetWeibull(self, double shape, double scale): return self.thisptr.GetWeibull(shape, scale) # Cauchy sample def GetCauchy(self, double median, double scale): return self.thisptr.GetCauchy(median, scale) # Student-t sample def GetStudentT(self, double degreesOfFreedom): return self.thisptr.GetStudentT(degreesOfFreedom) # The Laplace distribution is also known as the double exponential distribution. def GetLaplace(self, double mean, double scale): return self.thisptr.GetLaplace(mean, scale) # Log-normal sample def GetLogNormal(self, double mu, double sigma): return self.thisptr.GetLogNormal(mu, sigma) # Beta sample def GetBeta(self, double a, double b): return self.thisptr.GetBeta(a, b)
Again, very simple. The only thing I’ve added in this code is default values for the GetNormal()
method, specifying a mean of 0 and standard deviation of 1, since I’ll be using it a lot and it’s nice to have default values.
Now the only thing left is the setup file:
setup.py
from distutils.core import setup from distutils.extension import Extension from Cython.Distutils import build_ext setup( name = 'SimpleRNG', ext_modules=[ Extension("SimpleRNG", sources=["pySimpleRNG.pyx", "SimpleRNG.cpp"], # Note, you can link against a c++ library instead of including the source language="c++"), ], cmdclass = {'build_ext': build_ext}, )
And now calling it from IPython
run setup.py build_ext -i
And pleasantly, now, you can call the SetState(int,int)
function and generate the same set of random numbers in both C++ and Python. Which is absolutely wonderful for comparison / debugging. It’s been super useful for me, I hope someone else also finds it helpful!
All the code for this post can be found at my github repository: pySimpleRNG. If you’re simply looking for what you need to get SimpleRNG in Python, then all you need is the SimpleRNG.o file! Drop it into your Python project folder and import away.
Update: It was pointed out that having a Python script example would be useful, which is very true! Here’s a quick script using this shared library.
from SimpleRNG import pySimpleRNG a = pySimpleRNG() a.SetState(1,1) # set the seed a.GetUniform() # remember this number a.GetUniform() a.SetState(1,1) # reset seed a.GetUniform() # returns the same number as above
[…] this is going to be very similar to the code from the previous Cython posts, but in this one there’s no getting around it: We need to pass back and forth arrays. It […]
This is exactly what I needed, except I can’t seem to make use of it. Could you provide an example of usage in python? I keep getting errors while trying to call the SetInt function.
Hi Ben,
do you mean the SetState function? I’ve updated the blog post with a quick example script right at the end, please let me know if that doesn’t work for you!
Thanks!