Up

長周期乱数

独立な複数の系列への分割

 

別のサイトで説明しているL'Ecuyer の方法による乱数は、周期が約と長い。乱数を用いるシミュレーションの場合、乱数生成に掛かる時間よりも、生成した乱数に基づいて行われる計算時間の方が長いことが多い。また、周期が長いと、乱数生成の系列を適当に分割して、独立した複数の乱数生成系列を容易に得ることができる。このことについては本ウェブサイトの後半で説明する。

L'Ecuyer の方法による乱数の周期の長さをコンピュータ上での実行時間の長さで調べるため以下のPythonプログラムを作成した。

 

================= CheckTime.py ================

from datetime import datetime

from RNGen import RNp2to191 as MyRN         #   A random number generator class

 

def get_MicroSec():

    """

            Timer in microsecond

    """

    now = datetime.now()

    return now.microsecond + 1000000.0 * (now.second + 60.0 * (now.minute \

                            + 60.0 * now.hour))

 

rn = MyRN()

t0 = get_MicroSec()

 

for v in range(2**20):

    v = rn.uni()

 

t1 = get_MicroSec();

print("(2**20): ", t1 - t0, "microseconds")

s = 1000000.0 * 60.0 * 60.0 * 24.0 * 365.0

v = ((t1 - t0) * (2**191) / (2**20)) / s

print("(2**191): ", v, "years")

w = ((t1 - t0) * (2**100) / (2**20)) / s

print("(2**100): ", w, "years")

u = ((t1 - t0) * (2**50) / (2**20)) / s

print("(2**50): ", u, "years")

=======================================

 

上のプログラムでは、回一様乱数rn.uni()を呼び出したときの時間をもとに、周期である回呼び出すときの時間、および回呼び出すときの時間と回呼び出すときの時間を求めている。実行結果は図1のようになる。

図1

 

回の呼び出しに約3秒掛かっている。このとき、回の呼び出しには約年掛かるという結果である。1周期内の乱数すべて生成することは事実上不可能である。回で約年、回で約94年である。回で約年掛かるので、1周期個の長さの系列を個ずつの部分系列に分けて使えば、独立な複数の系列として扱える。このとき、最大個の独立な部分系列を得ることになる。

乱数生成のクラス型のアトリビュートjump(n)は、乱数生成を個先まで飛ぶものである。いま、3個の乱数生成オブジェクトrn_1rn_2rn_3を用意して、図2のように個ずつシフトする。

図2

 

例として、次のようなコードを作成した。このシフトにより3個の乱数生成オブジェクトrn_1rn_2rn_3は、3個の独立な乱数生成オブジェクトとして用いることができる。

 

from RNGen import RNp2to191 as MyRN

 

rn_1 = MyRN()

rn_2 = MyRN()

rn_2.jump(100)

rn_3 = MyRN()

rn_3.jump(100)

rn_3.jump(100)

print("{0:<20}{1:20}{2:20}".format('rn_1', 'rn_2', 'rn_3'))

for i in range(10):

    print("{0:<20.15f}{1:<20.15f}{2:<20.15f}".format(rn_1.uni(), rn_2.uni(), rn_3.uni()))

 

上のコードを実行すると図3のような結果になる。

図3

 

本ウェブサイトのプログラムは圧縮ファイルRN_Prgs.zipにまとめた。

乱数生成クラスのパッケージファイルRNGen.pyは以下のようである。

============== RNGen.py =====================

import math

 

#

#                    A progtram purely in Python

#                               of

#        Pierre L'Ecuyer's algorithm of random number generation

#

#                                       Yasuharu Okamoto,2016.09

#

#        cf. R. L. Eubank and A. Kupresanin (2012). "Statistical computing in C++ and R".

#

 

class RNp2to191:

    c209 = 2**32 - 209

    c22853 = 2**32 - 22853

 

    def __init__(self,*t):

        if (len(t) == 0):

            self.x10 = 3

            self.x11 = 2

            self.x12 = 1

            self.x20 = 6

            self.x21 = 5

            self.x22 = 4

        else:

            if (len(t) != 6):

                print("len(t) = %d"%(len(t)))

                raise "Number of parameters Error !"

            else:

                self.x10 = t[0]

                self.x11 = t[1]

                self.x12 = t[2]

                self.x20 = t[3]

                self.x21 = t[4]

                self.x22 = t[5]

 

    #

    #       Uniform distribution, 0 < uni() < 1

    #

    def uni(self):

        tx1 = (1403580 * self.x11 - 810728 * self.x12) % RNp2to191.c209

        self.x12 = self.x11

        self.x11 = self.x10

        self.x10 = tx1

        tx2 = (527612 * self.x20 - 1370589 * self.x22) % RNp2to191.c22853

        self.x22 = self.x21

        self.x21 = self.x20

        self.x20 = tx2

 

        z = (self.x10 - self.x20) % RNp2to191.c209

        if (z > 0):

            return z / (RNp2to191.c209 + 1.0)

        else:

            return RNp2t0191.c209 / (RNp2to191.c209 + 1.0)

 

    #

    #       Jump over 2**n numbers to be generated

    #

    def jump(self, n):

        if (n > 0):

            A = [[0, 1403580, -810728],

                 [1, 0, 0],

                 [0, 1, 0]]

            B = [[527612, 0, -1370589],

                 [1, 0, 0],

                 [0, 1, 0]]

            C = [[0, 0, 0],

                 [0, 0, 0],

                 [0, 0, 0]]

            for i in range(0, n):

                for j in range(0, 3):

                    for k in range(0, 3):

                        tv = 0

                        for h in range(0, 3):

                            tv = tv + A[j][h] * A[h][k]

                        C[j][k] = tv

                for j in range(0, 3):

                    for k in range(0, 3):

                        A[j][k] = C[j][k] % RNp2to191.c209

                       

                for j in range(0, 3):

                    for k in range(0, 3):

                        tv = 0

                        for h in range(0, 3):

                            tv = tv + B[j][h] * B[h][k]

                        C[j][k] = tv

                for j in range(0, 3):

                    for k in range(0, 3):

                        B[j][k] = C[j][k] % RNp2to191.c22853

 

            t0 = A[0][0] * self.x10 + A[0][1] * self.x11 + A[0][2] * self.x12

            t1 = A[1][0] * self.x10 + A[1][1] * self.x11 + A[1][2] * self.x12

            t2 = A[2][0] * self.x10 + A[2][1] * self.x11 + A[2][2] * self.x12

            self.x10 = t0 % RNp2to191.c209

            self.x11 = t1 % RNp2to191.c209

            self.x12 = t2 % RNp2to191.c209

            t0 = B[0][0] * self.x20 + B[0][1] * self.x21 + B[0][2] * self.x22

            t1 = B[1][0] * self.x20 + B[1][1] * self.x21 + B[1][2] * self.x22

            t2 = B[2][0] * self.x20 + B[2][1] * self.x21 + B[2][2] * self.x22

            self.x20 = t0 % RNp2to191.c22853

            self.x21 = t1 % RNp2to191.c22853

            self.x22 = t2 % RNp2to191.c22853

            

    #

    #             Standard Normal Distribution

    #       Rejection polar method for normal variate

    #

    def normal(self):

        w = 0.0

        v1 = 0.0

        while True:

            v1 = 2.0 * self.uni() - 1.0

            v2 = 2.0 * self.uni() - 1.0

            w = (v1 ** 2.0) + (v2 ** 2.0)

            if ((w < 1.0) and (w > 0.0)):

                break

        c = math.sqrt(-2.0 * math.log(w) / w)

 

        return c * v1

   

 

    #

    #       Normal Distribution of mean m  and standard deviation s

    #

    def normalMS(self, m, s):

        return s * self.normal() + m

 

 

    #

    #       A Pair of Independent Random Numbers of

    #          the Standard Normal Distribution

    #

    def normalPair(self):

        w = 0.0

        v1 = 0.0

        v2 = 0.0

        while True:

            v1 = 2.0 * self.uni() - 1.0

            v2 = 2.0 * self.uni() - 1.0

            w = (v1 ** 2.0) + (v2 ** 2.0)

            if ((w < 1.0) and (w > 0.0)):

                break

        c = math.sqrt(-2.0 * math.log(w) / w)

 

        return c * v1, c * v2

====================================

 

 

 

 

Up