Random 查找64位LCG(MMIX(由Knuth确定))的更多独立种子值

Random 查找64位LCG(MMIX(由Knuth确定))的更多独立种子值,random,parallel-processing,numbers,overflow,random-seed,Random,Parallel Processing,Numbers,Overflow,Random Seed,我使用的是64位LCG(MMIX(由Knuth提供))。它在我的代码中生成一个特定的随机数块,这些随机数用来执行一些操作。我的代码在单核中工作,我希望将工作并行化以减少执行时间。 从这个意义上讲,在开始考虑更高级的方法之前,我想简单地并行执行更多相同的代码(事实上,代码在一定数量的独立模拟上重复相同的任务,因此我可以简单地将模拟的数量划分为更多相同的代码并并行运行)。 我现在唯一的问题是为每个代码找到一个种子;特别是,为了避免在不同代码中生成的数据之间出现不必要的非平凡相关性,我必须确保在不同代

我使用的是64位LCG(MMIX(由Knuth提供))。它在我的代码中生成一个特定的随机数块,这些随机数用来执行一些操作。我的代码在单核中工作,我希望将工作并行化以减少执行时间。
从这个意义上讲,在开始考虑更高级的方法之前,我想简单地并行执行更多相同的代码(事实上,代码在一定数量的独立模拟上重复相同的任务,因此我可以简单地将模拟的数量划分为更多相同的代码并并行运行)。
我现在唯一的问题是为每个代码找到一个种子;特别是,为了避免在不同代码中生成的数据之间出现不必要的非平凡相关性,我必须确保在不同代码中生成的随机数不会重叠。为此,从第一个代码中的某个种子开始,我必须找到一种方法来找到一个非常遥远的值(下一个种子),不是绝对值,而是伪随机序列(因此,要从第一个种子到第二个种子,我需要大量的LCG步骤)。
我的第一次尝试是:
从序列中生成的两个连续数字之间的LCG关系开始

因此,原则上,我可以计算上述关系,例如,n=2^40,I_0等于第一个种子的值,并从第一个种子获得随机CLG序列中距离2^40步的新种子。
问题是,这样做的话,我必须计算a^n。事实上,对于MMIX(由Knuth)a~2^62,我使用无符号long-long-int(64位)。请注意,这里唯一的问题是上述关系中的分数。如果只有求和和和乘法,我可以忽略溢出问题,因为有以下模块属性(实际上我使用2^64作为c(64位生成器)):

那么,从一个特定的值(第一个种子)开始,我如何在LC伪随机序列中找到第二个远离大量步长的值呢

[编辑]
r3mainer解决方案非常适合python代码。我现在尝试用c语言实现它,使用无符号的int128变量。我只有一个问题:原则上我应该计算:

为了简单起见,我想计算:

n=2^40,c(a-1)~2^126。我继续一个循环。从
temp=a
开始,在每次迭代中我计算
temp=temp*temp
,然后我计算
temp%c(a-1)
。问题出现在第二步(
temp=temp*temp
temp
实际上,原则上可以是任何数字temp是一个大数字,比如说>2^64,我将进入溢出状态,在下一个模块操作之前达到2^128-1。那么有没有办法避免呢?目前,我看到的唯一解决方案是使用位上的循环执行每个乘法,如下所示: 乘法过程中是否有其他方式执行模块运算?

(注意,由于c=2^64,使用mod(c)操作,我没有相同的问题,因为溢出点(对于ull int变量)与模块重合)

任何形式为
x[n+1]=(x[n]*a+c)%m
的LCG都可以非常快速地跳过到任意位置

从种子值零开始,LCG的前几次迭代将提供以下序列:

x₀ = 0
x₁ = c % m
x₂ = (c(a + 1)) % m
x₃ = (c(a² + a + 1)) % m
x₄ = (c(a³ + a² + a + 1)) % m
很容易看出,每个项实际上都是一个几何级数的和,可以用以下公式计算:

(a^n-1)
项可以通过快速计算,但是除以
(a-1)
有点棘手,因为
(a-1)
m
都是偶数(即,不是互质),所以我们不能直接计算
(a-1)mod m
的值

相反,计算
(a^n-1)mod m*(a-1)
,然后将结果直接(非模)除以
a-1
。在Python中,计算如下:

def lcg_跳过(m、a、c、n):
#使用参数m(模数)计算LCG序列的第n项,
#a(乘数)和c(增量),假设初始种子为零
a1=a-1
t=pow(a,n,m*a1)-1
t=(t*c//a1)%m
返回t
def测试(nsteps):
m=2**64
a=6364136223846793005
c=1442690504088963407
#
打印(“暴力计算:”)
种子=0
对于范围内的i(nsteps):
种子=(种子*a+c)%m
打印(种子)
#
打印(“通过快速方法计算:”)
#用模幂法计算第n项
打印(lcg_跳过(m、a、c、NSTEP))
测试(1000000)

因此,要创建具有非重叠输出序列的lcg,您需要做的就是使用由
lcg_skip()
生成的初始种子值,这些值的
n
距离足够远。

对于lcg,已知的属性是在O(log2(n))时间内向前和向后跳跃,其中n是跳跃点之间的距离,F.Brown的论文,“任意步长的随机数生成”,转译。是Nucl。Soc。(1994年11月)

这意味着如果你有满足赫尔-多贝尔定理的LCG参数(a,c),那么整个周期将是264个数字,然后再重复它们自己,也就是说,对于
Nt
number个线程,你将跳转距离设为264/Nt,所有线程都从相同的种子开始,并在初始化LCG(264/Nt)*threadId之后跳转,由于序列重叠,你将完全不受RNG相关性的影响

对于在NumPy中实现的公共64无符号模数学的最简单情况,下面的代码应该可以正常工作

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')

seed = np.uint64(1)

rng64 = LCG(seed, np.uint64(6364136223846793005), np.uint64(1))

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())

在Python 3.9.1中测试,x64 Win 10

谢谢r3mainer;有了这个,我只需要一个128位的变量(用来存储m*a1)。所以我可以继续使用一个简单的c函数,它用一个/(m*a1)步迭代地执行幂指数运算。您是在python中实现的,因此在python中也可以处理128位(或者更大的大小)v
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')

seed = np.uint64(1)

rng64 = LCG(seed, np.uint64(6364136223846793005), np.uint64(1))

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())