Upen

長周期乱数(LEcuyer)のPythonプログラム

合同式を用いる乱数発生法は、Pythonでは整数型に桁数の制限が文法上は無いので、実行速度を無視すれば、式のままプログラムを書くことができる。例えば、LEcuyer の方法(cf. Eubank & Kupresanin, 2012. Statistical computing in C++ and R.)は次の2つの式

に基づくものであるが、これはPythonでは次のように

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

        self.x12 = self.x11

        self.x11 = self.x10

        self.x10 = tx1

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

        self.x22 = self.x21

        self.x21 = self.x20

        self.x20 = tx2

簡単に書くことができる。具体的には、ファイルRNGen.pyにクラス型として宣言したが、上の説明と直接関係する部分は以下のようである。

==================  RNGen.pyの一部  =======================

 

#

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

 

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

 

乱数生成のオブジェクトは、初期化時に6個の値を設定することができる。初期化時の の値が全てが0ということではなく、かつより小さい、および初期化時の の値が全てが0ということではなく、かつより小さいとき、乱数の周期は約である。本ウェブサイトのプログラムファイルRNGen.pyなどは、圧縮ファイルrngprgs.zipにまとめたが、ダウンロード解凍して、利用者の責任において、自由に利用されたい。

乱数生成のクラスRNp2to191の使用例のプログラムを示す。

 

=================  CheckRN.py  ======================

from RNGen import RNp2to191 as rng

 

rn1 = rng()

rn2 = rng(10, 20, 30, 40, 50, 60)       #       The seeds are set

print("rn1 = %d, %d, %d, %d, %d, %d"%(rn1.x10, rn1.x11, rn1.x12,rn1.x20,rn1.x21,rn1.x22))

print("rn2 = %d, %d, %d, %d, %d, %d"%(rn2.x10, rn2.x11, rn2.x12,rn2.x20,rn2.x21,rn2.x22))

 

rn_0 = rng()        #   No jumps

rn_2 = rng()

rn_2.jump(2)        #   2**2 jumps

rn_22 = rng()

rn_22.jump(2)       #   2**2 jumps

rn_22.jump(2)       #   2**2 jumps, again

rn_4 = rng()

rn_4.jump(4)        #   2**4 jumps

for i in range(0, 20):

    print("%d %20.15f %20.15f %20.15f %20.15f"%(i, rn_0.uni(), rn_2.uni(), rn_22.uni(), rn_4.uni()))

 

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

 

上のプログラムでは

from RNGen import RNp2to191 as rng

とクラスRNp2to191importしているので、

rn1 = rng()

rn2 = rng(10, 20, 30, 40, 50, 60)

と名前rngによってクラス型が示されている。オブジェクトrn1の初期値はデフォルト値であり、rn2は初期値が指定されている、このことは

print("rn1 = %d, %d, %d, %d, %d, %d"%(rn1.x10, rn1.x11, rn1.x12,rn1.x20,rn1.x21,rn1.x22))

print("rn2 = %d, %d, %d, %d, %d, %d"%(rn2.x10, rn2.x11, rn2.x12,rn2.x20,rn2.x21,rn2.x22))

による出力によって確認することができる。

プログラム後半における関数jump(n)は、乱数生成において途中の個の乱数を飛び越えるものである。これは、以下の関係を利用している。

例えば、式(1)の関係式は行列を使って以下のように表すことができる。

したがって、

とおけば、

と書けるので、乱数の生成は行列を左から掛けることとして表すことができる。したがって、m回後の乱数はを左から掛けることによって得られる。関数jump(n)は、を求めて掛けている。これは、行列を2乗することをn回繰り返すことによって得られる。関数jumpのコードは以下のようである。

===================  RNGen.pyの一部  ====================

 

    #

    #       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

            

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

 

上のプログラムCheckRN.pyを実行すると図1に示すような結果になる。

図1

 

2つの乱数オブジェクトrn1rn2の初期値が出力され、その後、関数jumpの効果を見る出力が表示されている。

関数jumpによって乱数列を分割して部分列に分け、それぞれを独立した乱数として別個のオブジェクトで表せば、独立した乱数列を複数個使うことができる。どれくらいの部分列に分ければよいかの目安を得るため、個の乱数を生成する時間を測るプログラムCheckTime.pyを作成した。

 

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

import time

from RNGen import RNp2to191 as rng

 

print("I am working...\n")

rn = rng()

t0 = time.time()

n = 2**23

for i in range(0,n):

    rn.uni()

t1 = time.time()

print("Generation of 2**23 random numbers took about %dsec."%(t1 - t0))

 

t = (t1 - t0) * ((2.0**100) / (2.0**23))

days = t / (24.0 * 60.0 * 60.0)

print("Generation of 2**100 random numbers will take about %dsec.,"%t +

      "i.e.,\n                                              about%8.1edays."%days)

 

print("2**23 = %d"%(2**23))

print("2**100 = %d"%(2**100))

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

 

上のプログラムの実行結果を図2に示す。

図2

 

乱数を個生成するのに掛かる日数が約日であるとの結果である。現実問題として、個の乱数を使い切ることはない。関数jump(100)で部分列に区切って使えば、独立な乱数列が得られることになる。乱数全体の周期はであるので、個の乱数で区切った場合、個の部分列が使用可能である。

 

 

Up