繁体   English   中英

为 64 位 LCG (MMIX (by Knuth)) 找到更多独立的种子值

[英]find more indipendent seed value for a 64 bit LCG (MMIX (by Knuth))

我正在使用 64 位 LCG (MMIX (by Knuth))。 它在我的代码中生成一定的随机数块,使用它们来执行一些操作。 我的代码在单核中工作,我想并行化工作以减少执行时间。
在开始从这个意义上考虑更高级的方法之前,我想简单地并行执行更多相同的代码(事实上,代码在一定数量的独立模拟中重复相同的任务,所以我可以简单地将模拟数量拆分为更多相同的代码并并行运行它们)。
我现在唯一的问题是为每个代码找到一个种子; 特别是,为了避免在不同代码中生成的数据之间出现不必要的非平凡相关性,我必须确保在各种代码中生成的随机数不会重叠。 为此,从第一个代码中的某个种子开始,我必须找到一种方法来找到一个非常遥远的值(下一个种子),不是绝对值,而是伪随机序列(因此,从 go第一到第二种子,我需要大量的LCG步骤)。
我的第一次尝试是这样的:
从序列中生成的2个连续数字之间的LCG关系开始
在此处输入图像描述
因此,原则上,我可以计算上述关系,例如,n = 2^40 和 I_0 等于第一个种子的值,并从第一个随机 CLG 序列中获得一个距离 2^40 步的新种子.
问题是,这样做,我在溢出计算 a^n 时需要 go。 事实上,对于 MMIX(由 Knuth)a~2^62,我使用 unsigned long long int(64 位)。 请注意,这里唯一的问题是上述关系中的分数。 如果只有求和和乘法,由于以下模块化属性,我可以忽略溢出问题(实际上我使用 2^64 作为 c(64 位生成器)):

在此处输入图像描述

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

[编辑]
r3mainer 解决方案非常适合 python 代码。 我现在正在尝试使用无符号的 __int128 变量在 c 中实现它。 我只有一个问题:原则上我应该计算:
在此处输入图像描述

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

在此处输入图像描述

n = 2^40 和 c(a-1)~2^126。 我继续一个循环。从temp = a开始,在每次迭代中我计算temp = temp*temp ,然后我计算temp%c(a-1) 问题出在第二步( temp = temp*temp )。 temp实际上可以是,原则上任何数字 < c(a-1)~2^126。 如果temp是一个大数字,例如 > 2^64,我将在下一个模块操作之前溢出 go,达到 2^128 - 1。 那么有没有办法避免呢? 目前我看到的唯一解决方案是使用循环位执行每个乘法,如此处所建议: c 代码:防止在具有巨大模块的模块化操作中溢出(靠近溢出阈值的模块)是否有另一种方法可以在乘法?
(注意 c = 2^64,使用 mod(c) 操作我没有同样的问题,因为溢出点(对于 ull int 变量)与模块重合)

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

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

x₀ = 0
x₁ = c % m
x₂ = (c(a + 1)) % m
x₃ = (c(a² + a + 1)) % m
x₄ = (c(a³ + a² + a + 1)) % m

很容易看出,每一项实际上是一个几何级数的总和,可以用一个简单的公式来计算:

x_n = (c(a^{n-1} + a^{n-2} + ... + a + 1)) % m
    = (c * (a^n - 1) / (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 中,计算 go 是这样的:

def lcg_skip(m, a, c, n):
    # Calculate nth term of LCG sequence with parameters m (modulus),
    # a (multiplier) and c (increment), assuming an initial seed of zero
    a1 = a - 1
    t = pow(a, n, m * a1) - 1
    t = (t * c // a1) % m
    return t

def test(nsteps):
    m = 2**64
    a = 6364136223846793005
    c = 1442695040888963407
    #
    print("Calculating by brute force:")
    seed = 0
    for i in range(nsteps):
        seed = (seed * a + c) % m
    print(seed)
    #
    print("Calculating by fast method:")
    # Calculate nth term by modular exponentiation
    print(lcg_skip(m, a, c, nsteps))

test(1000000)

因此,要创建具有非重叠 output 序列的 LCG,您需要做的就是使用lcg_skip()生成的初始种子值,其n值相距足够远。

好吧,对于 LCG,已知的特性是在 O(log 2 (N)) 时间内向前和向后跳跃,其中 N 是跳跃点之间的距离,F. Brown 的论文,“任意步长的随机数生成”,Trans。 是。 核。 社会党。 (1994 年 11 月)。

这意味着如果您有满足 Hull-Dobell 定理的 LCG 参数 (a, c),那么整个周期将是 2 64 个数字,然后再重复它们自己,并说对于Nt个数 pf 线程,您的跳跃距离为 2 64 / Nt,并且所有线程从相同的种子开始,并在通过 (2 64 / Nt)*threadId 初始化 LCG 后跳转,由于序列重叠,您将完全免受 RNG 相关性的影响。

对于常见的 64 无符号模数学的最简单情况,如 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')

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 中测试

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM