RNG技术的可移植性和可重复性

时间:2019-07-11 06:59:05

标签: python algorithm math random

我可以使用两种方法之一来创建具有两个重要特征的伪随机数序列-(1)它可以在不同的机器上重现,并且(2)该序列永远不会在范围内重复任何数字,直到发出所有数字为止

我的问题是-这两种方法在可移植性(操作系统,Python版本等)方面是否都存在潜在问题?例如,有人知道在XXX为true的情况下,我是否会在一个系统上得到一组结果,而在另一个系统上得到不同的结果?

我并没有真正征求关于使用哪种方法的建议,只有当Z为真时,我应该注意Y系统上的X。

我已经尝试了几种Linux版本,全部为64位,并且看起来似乎一致,但是我无法轻松访问Windows或32位版本。

请注意,它们产生的距离互不相同,但这对我来说是可以的。这些数字只需要在人眼中随机出现即可。

方法1
使用本机Python库函数从范围生成随机样本。如果我使用大范围(10m或更大),速度会很慢,但是对于相对较小的范围来说还可以,并且对那些没有数学学位的人来说更容易理解:

import random
random.seed(5)
x = random.sample(range(10000,99999),89999)
for i in range(10):
   print(x[i])

方法2
使用不是来自Python库的算法:
https://en.wikipedia.org/wiki/Linear_congruential_generator
即使在很大的范围内,它也非常快,但是很难理解,因此可以发现潜在的问题:

def lcg(modulus, a, c, seed):
  while True:
    seed = (a * seed + c) % modulus
    yield seed


m = 10000019
c = int(m/2)
a = 5653
s = a

g = lcg(m,a,c,s)
for _ in range(10):
  print(next(g))

请注意,我对替代品持开放态度;最初的问题在这里被问到:https://math.stackexchange.com/questions/3289084/generate-a-pseudo-random-predictable-non-repeating-integer-sequence-purely-math

3 个答案:

答案 0 :(得分:1)

大多数便携式版本IMO将是LCG,其周期等于机器的自然字大小。它为模块使用寄存器溢出,从而使其更快。您必须使用NumPy数据类型来做到这一点,这是简单的代码,常量a,c取自表4 here

import numpy as np

def LCG(seed: np.uint64, a: np.uint64, c: np.uint64) -> np.uint64:
    with np.errstate(over='ignore'):
        while True:
            seed = (a * seed + c)
            yield seed

a = np.uint64(2862933555777941757)
c = np.uint64(1)

rng64 = LCG(np.uint64(17), a, c)

print(next(rng64))
print(next(rng64))
print(next(rng64))

Linux x64和Windows x64以及OS X VM的工作原理完全相同。

关于可重复性,唯一的好处是存储第一对数字,并在应用初始化阶段将它们与LCG输出进行比较-如果可以,请继续进行操作。

我喜欢的LCG的另一个功能是它能够以log 2 (N)的时间向前跳,其中N是跳过的次数。我可以为您提供执行此操作的代码。通过使用跳转,您可以确保并行独立随机流的不重叠序列

更新

这是将我的C代码转换为Python / NumPy的方法,似乎可以正常工作。在对数时间,它可以向前和向后跳过。

import numpy as np

class LCG(object):

    UZERO: np.uint64 = np.uint64(0)
    UONE : np.uint64 = np.uint64(1)

    def __init__(self, seed: np.uint64, a: np.uint64, c: np.uint64) -> None:
        self._seed: np.uint64 = np.uint64(seed)
        self._a   : np.uint64 = np.uint64(a)
        self._c   : np.uint64 = np.uint64(c)

    def next(self) -> np.uint64:
        self._seed = self._a * self._seed + self._c
        return self._seed

    def seed(self) -> np.uint64:
        return self._seed

    def set_seed(self, seed: np.uint64) -> np.uint64:
        self._seed = seed

    def skip(self, ns: np.int64) -> None:
        """
        Signed argument - skip forward as well as backward

        The algorithm here to determine the parameters used to skip ahead is
        described in the paper F. Brown, "Random Number Generation with Arbitrary Stride,"
        Trans. Am. Nucl. Soc. (Nov. 1994). This algorithm is able to skip ahead in
        O(log2(N)) operations instead of O(N). It computes parameters
        A and C which can then be used to find x_N = A*x_0 + C mod 2^M.
        """

        nskip: np.uint64 = np.uint64(ns)

        a: np.uint64 = self._a
        c: np.uint64 = self._c

        a_next: np.uint64 = LCG.UONE
        c_next: np.uint64 = LCG.UZERO

        while nskip > LCG.UZERO:
            if (nskip & LCG.UONE) != LCG.UZERO:
                a_next = a_next * a
                c_next = c_next * a + c

            c = (a + LCG.UONE) * c
            a = a * a

            nskip = nskip >> LCG.UONE

        self._seed = a_next * self._seed + c_next    


np.seterr(over='ignore')

a = np.uint64(2862933555777941757)
c = np.uint64(1)
seed = np.uint64(1)

rng64 = LCG(seed, a, c) # initialization

print(rng64.next())
print(rng64.next())
print(rng64.next())

rng64.skip(-3) # back by 3
print(rng64.next())
print(rng64.next())
print(rng64.next())

rng64.skip(-3) # back by 3
rng64.skip(2) # forward by 2
print(rng64.next())

无论如何,LCG RNG的摘要:

  1. 具有良好的常量(请参阅L'Ecuyer论文参考),它将覆盖整个[0 ... 2 64 )范围,而无需重复自身。基本完美的[0 ... 2 64 )-> [0 ... 2 64 )映射,您可以设置 0,1,2,3,...作为输入并获得整个范围的输出
  2. 这是可逆的,您可以获取以前的种子,因此映射实际上是 bijection,[0 ... 2 64 )<-> [0 ... 2 64 )。有关详情,请参见Reversible pseudo-random sequence generator
  3. 它具有对数向前和向后跳跃,因此没有问题可以找到 并行计算的合适间隔-从单个种子开始,然后下一个线程将被跳过(种子,2 64 / N),下一个线程将被跳过(种子,2 64 / N * 2),依此类推。保证不会重叠
  4. 虽然不是高质量的RNG,但操作简单,快速

答案 1 :(得分:0)

LCG很不错。如果您想使LCG更易于理解,则可以递归地实现它,而不是迭代地突出显示它所基于的递归公式。仅在您不太担心复杂性的情况下才这样做。

否则,我认为方法2对于PRNG足够清晰。

答案 2 :(得分:0)

算法(尤其是伪随机数生成器)可以通过多种方式在计算机上使用inconsistent results。最值得注意的方法是该算法是否依赖浮点数(几乎总是精度有限)和浮点舍入。

某些编程语言比其他编程语言更容易发生可移植性问题。例如,与Python不同,在C或C ++中,由于-

  • 有符号整数溢出的未定义行为,或
  • 某些数据类型(尤其是intlong)的长度在各个编译器中的定义方式不同。

我不知道方法2中的Python代码可以在计算机之间传递不一致的结果的任何方式。另一方面,方法1是否可以实现取决于random.sample是否在您关心的所有Python版本以及所有计算机上以相同的方式实现。