multithreading - Manera canónica de generar números aleatorios en Cython
random (1)
Creo que la manera más fácil de hacerlo es utilizar la biblioteca estándar C ++ 11 que proporciona buenos generadores de números aleatorios encapsulados y formas de usarlos . Por supuesto, esta no es la única opción, y podría envolver prácticamente cualquier biblioteca de C / C ++ adecuada (una buena opción podría ser usar cualquier biblioteca que numpy use, ya que es muy probable que ya esté instalada).
Mi consejo general es solo envolver los bits que necesita y no molestar con la jerarquía completa y todos los parámetros opcionales de la plantilla. A modo de ejemplo, he mostrado uno de los generadores predeterminados, alimentado a una distribución de flotación uniforme.
# distutils: language = c++
# distutils: extra_compile_args = -std=c++11
cdef extern from "<random>" namespace "std":
cdef cppclass mt19937:
mt19937() # we need to define this constructor to stack allocate classes in Cython
mt19937(unsigned int seed) # not worrying about matching the exact int type for seed
cdef cppclass uniform_real_distribution[T]:
uniform_real_distribution()
uniform_real_distribution(T a, T b)
T operator()(mt19937 gen) # ignore the possibility of using other classes for "gen"
def test():
cdef:
mt19937 gen = mt19937(5)
uniform_real_distribution[double] dist = uniform_real_distribution[double](0.0,1.0)
return dist(gen)
(El -std=c++11
al comienzo es para GCC. Para otros compiladores puede que necesite ajustar esto. De todos modos, cada vez más c ++ 11 es un valor predeterminado, por lo que puede soltarlo)
Con referencia a sus criterios:
- Plataforma cruzada en cualquier cosa que admita C ++. Creo que la secuencia debe especificarse para que sea repetible.
- El hilo es seguro, ya que el estado se almacena por completo dentro del objeto
mt19937
(cada hilo debe tener su propiomt19937
). - No GIL - es C ++, sin partes de Python
- Razonablemente fácil.
Editar: sobre el uso de discrete_distribution
.
Esto es un poco más difícil porque los constructores para discrete_distribution
son menos obvios sobre cómo envolver (implican iteradores). Creo que lo más fácil es ir a través de un vector C ++ ya que el soporte para eso está integrado en Cython y es fácilmente convertible a / desde una lista de Python
# use Cython''s built in wrapping of std::vector
from libcpp.vector cimport vector
cdef extern from "<random>" namespace "std":
# mt19937 as before
cdef cppclass discrete_distribution[T]:
discrete_distribution()
# The following constructor is really a more generic template class
# but tell Cython it only accepts vector iterators
discrete_distribution(vector.iterator first, vector.iterator last)
T operator()(mt19937 gen)
# an example function
def test2():
cdef:
mt19937 gen = mt19937(5)
vector[double] values = [1,3,3,1] # autoconvert vector from Python list
discrete_distribution[int] dd = discrete_distribution[int](values.begin(),values.end())
return dd(gen)
Obviamente eso es un poco más complicado que la distribución uniforme, pero no es imposible (y las partes desagradables podrían estar ocultas dentro de una función de Cython).
Cuál es la mejor manera de generar números aleatorios pseudo uniformes (un doble en [0, 1)] que es:
- Plataforma cruzada (idealmente con la misma secuencia de muestra)
- Thread safe (paso explícito del estado mutado del prng o usando un estado local de threads)
- Sin bloqueo GIL
- Fácilmente envolvente en Cython
Hubo una publicación similar hace más de 3 años sobre esto, pero muchas de las respuestas no cumplen todos los criterios. Por ejemplo, drand48
es específico de POSIX.
El único método que conozco, que parece (pero no está seguro) para cumplir con todos los criterios es:
from libc.stdlib cimport rand, RAND_MAX
random = rand() / (RAND_MAX + 1.0)
Nota @ogrisel hizo la misma pregunta hace 3 años.
Editar
Llamar rand no es seguro para subprocesos. Gracias por señalar eso a @DavidW.