2012年11月12日月曜日

WELL512a, WELL1024a を Pythonで実装

前回の続きです。

前回のコードは、元にしたプログラムがゲーム用に最適化されちゃっていて、もとの論文と比較しにくかったので、乱数生成部分を書き直しました。

WELL512a と WELL1024a, はC版と同じ結果が返ることを確認しています。
それ以外は、たぶん動くけど未検証なので、コメントアウトしてあります。
(誰か検証してください!)

あと、jumpahead()は、未実装にしました。seed()使って下さい。
本来の意味だと、数学的に離れた位置にジャンプするべきなのですが、
やり方がわからないんで、前はseed()を使ってお茶を濁してました。
これは、かえって混乱のもとなのでやめました。


# -*- coding: utf-8  -*-
# ---------------------------------------------------------------------------
# WELL(512a, 1024a)のPython実装
#
# 元の論文との対応付けのし易さを最重視して実装しています。
# 状態の初期化にSHA512を使うのがオリジナルのアイディアです。
# 512より多い状態を初期化する際を考慮して、
# SHA512の繰り返しとXorShiftのXORで状態を初期化しています。
#
# パフォーマンスを求める方は、CやC++版を使って下さい。
#
# このコードを作成するにあたり、参考にしたのは、以下です
#
#   - http://d.hatena.ne.jp/fgshun/20090724/1248446400
#   - http://www.iro.umontreal.ca/~panneton/WELLRNG.html
#   - http://www3.ocn.ne.jp/~harase/megenerators.html
#   - http://bitbucket.org/sergiu/random/src/tip/well.hpp
#   - http://en.wikipedia.org/wiki/Well_Equidistributed_Long-period_Linear
#
# このコードのライセンスは、MITでお願いします。
#
# ---------------------------------------------------------------------------

from random import Random as _Random
from os import urandom as _urandom
from binascii import hexlify as _hexlify
from hashlib import sha512 as _sha512
from itertools import cycle as _cycle, izip as _izip

# ---------------------------------------------------------------------------
# 各WELLのベースクラス定義
#
# random_int32()が未実装なので、単独では使用できません。
# init_subclassと_M1~_M6を使用して、random_int32()を初期化して使って下さい。
# ---------------------------------------------------------------------------

def _xor128(x=123456789, y=362436069, z=521288629, w=88675123):
    while True:
        t = x^((x << 11) & 0xffffffff)
        x = y
        y = z
        z = w
        w = (w ^ (w >> 19)) ^ (t ^ (t >> 8))
        yield w


class _WELLBase(_Random):

    _W = 32
    _MASK = 2 ** _W - 1

    def __init__(self, seed=None):

        self.state = self._gen_seed(seed)
        self._check_zerostate()

        self.gauss_next = None
        self.index = 0

    def _check_zerostate(self):
        if self.state == [0] * self._R: 
            self.state = self._gen_seed(self.VERSION)

    @classmethod
    def _gen_seed(cls, a=None):
        if a is None:
            try:
                a = int(_hexlify(_urandom(16)), 16)
            except NotImplementedError:
                import time
                a = int(time.time() * 256)

        if not isinstance(a, basestring):
            a = str(a)

        hashed_states = _sha512(a).hexdigest()
        states = [int(hashed_states[i: i+8], 16) for i in range(0, 128, 8)]
        return [xor128 ^ sha512 for (i, xor128, sha512) in _izip(range(cls._R), _xor128(), _cycle(states))]

    def seed(self, a=None):
        self.__init__(a)

    def random_int32(self):
        raise NotImplementedError

    def random(self): 
        return self.random_int32() * 2.3283064365386963e-10

    def random53(self): 
        return self.getrandbits(53) * (1.0 / 9007199254740992.0)

    def getstate(self): 
        return (self.VERSION, tuple(self.state),
                self.gauss_next, self.index) 

    def setstate(self, state): 
        version = state[0] 
        if version == self.VERSION: 
            self.state = list(state[1])
            self.gauss_next = state[2]
            self.index = state[3] 
        else: 
            raise ValueError("state with version %s passed to "
                             "setstate() of version %s" %
                             (version, self.VERSION)) 

    def jumpahead(self, n):
        raise NotImplementedError

    def getrandbits(self, k=32):
        ret = self.random_int32()
        for _ in range((k - 1) // 32): 
            ret = (ret << 32) ^ self.random_int32()
        return ret >> ((- k) % 32)

    @classmethod
    def init_random_int32(cls, T0, T1, T2, T3, T4, T5, T6, T7, m1, m2, m3, me_mask=0):

        MASKU = cls._MASK >> (cls._W - cls._P)
        MASKL = cls._MASK << cls._P
        r = cls._R

        def random_int32(self):

            s = self.state
            newV1 = i = self.index
            newV0 = i_VRm1 = (i - 1) % r
            i_VRm2 = (i - 2) % r
            vm2 = s[(i + m2) % r]

            z0 = (s[i_VRm1] & MASKL) | (s[i_VRm2] & MASKU);
            z1 = T0(s[i]) ^ T1(s[(i + m1) % r])
            z2 = T2(vm2 ) ^ T3(s[(i + m3) % r])
            s[newV1] = z3 = z1 ^ z2
            s[newV0] = z4 = T4(z0) ^ T5(z1) ^ T6(z2) ^ T7(z3)
            self.index = i_VRm1

            return z4 ^ (vm2 & me_mask)

        cls.random_int32 = random_int32

    @classmethod
    def _output_after_tempering(cls, b, c):
        random_int32 = cls.random_int32
        def random_int32_after_tempering(self):
            y = random_int32(self)
            y = y ^ ((y << 7) & b)
            y = y ^ ((y << 15) & c)
            return y
        cls.random_int32 = random_int32_after_tempering

    @classmethod
    def _shift(cls, k):
        MASK = cls._MASK
        if k >= 0:
            return lambda v: (v >> k)
        else:
            k_abs = abs(k)
            return lambda v: (v << k_abs) & MASK

    @classmethod
    def _M0(cls):
        return lambda v: 0

    @classmethod
    def _M1(cls):
        return lambda v: v

    @classmethod
    def _M2(cls, t):
        return cls._shift(t)

    @classmethod
    def _M3(cls, t):
        t_shift = cls._shift(t)
        return lambda v: v ^ t_shift(v)

    @classmethod
    def _M4(cls, a):
        w1 = cls._W - 1
        return lambda x: (x >> 1) ^ ((x & 1) * a)

    @classmethod
    def _M5(cls, t, b):
        t_shift = cls._shift(t)
        return lambda v: v ^ (t_shift(v) & b)

    @classmethod
    def _M6(cls, r, a, ds, dt):
        q_shift = cls._shift( - r)
        wq_shift = cls._shift(cls._W - r)
        return lambda x: ((q_shift(x) ^ wq_shift(x)) & ds) ^ (a if (x & dt) != 0 else 0)

# ------------------------------------------------------
# 個別のWELLの定義
# ------------------------------------------------------



class WELL512a(_WELLBase):
    VERSION = u"WELL512a ver0"
    _K = 512  # not use
    _R = 16
    _P = 0

WELL512a.init_random_int32(
    m1=13, m2=9, m3=5,
    T0=WELL512a._M3(-16), T4=WELL512a._M3(-2),
    T1=WELL512a._M3(-15), T5=WELL512a._M3(-18),
    T2=WELL512a._M3(11),  T6=WELL512a._M2(-28),
    T3=WELL512a._M0(),    T7=WELL512a._M5(-5, 0xda442d24))



class WELL1024a(_WELLBase):
    VERSION = u"WELL1024a ver0"
    _K = 1024  # not use
    _R = 32
    _P = 0

WELL1024a.init_random_int32(
  m1=3, m2=24, m3=10,
  T0=WELL1024a._M1(),    T4=WELL1024a._M3(-11),
  T1=WELL1024a._M3(8),   T5=WELL1024a._M3(-7),
  T2=WELL1024a._M3(-19), T6=WELL1024a._M3(-13),
  T3=WELL1024a._M3(-14), T7=WELL1024a._M0())


# 以下、未検証のためコメントアウト
#
#class WELL1024b(_WELLBase):
#    VERSION = u"WELL1024b ver0"
#    _K = 1024  # not use
#    _R = 32
#    _P = 0
#
#WELL1024b.init_random_int32(
#  m1=22, m2=25, m3=26,
#  T0=WELL1024b._M3(-21),        T4=WELL1024b._M3(-14),
#  T1=WELL1024b._M3(17),         T5=WELL1024b._M3(-21),
#  T2=WELL1024b._M4(0x8bdcb91e), T6=WELL1024b._M1(),
#  T3=WELL1024b._M3(15),         T7=WELL1024b._M0())
#
#class WELL19937a(_WELLBase):
#    VERSION = u"WELL19937a ver0"
#    _K = 19937  # not use
#    _R = 624
#    _P = 31
#
#WELL19937a.init_random_int32(
#  m1=70, m2=170, m3=449,
#  T0=WELL19937a._M3(-25), T4=WELL19937a._M1(),
#  T1=WELL19937a._M3(27),  T5=WELL19937a._M3(-9),
#  T2=WELL19937a._M2(9),   T6=WELL19937a._M3(-21),
#  T3=WELL19937a._M3(1),   T7=WELL19937a._M3(21))
#
#class WELL19937b(_WELLBase):
#    VERSION = u"WELL19937b ver0"
#    _K = 19937  # not use
#    _R = 624
#    _P = 31
#
#WELL19937b.init_random_int32(
#  m1=203, m2=613, m3=123,
#  T0=WELL19937b._M3(7),   T4=WELL19937b._M3(-19),
#  T1=WELL19937b._M1(),    T5=WELL19937b._M2(-11),
#  T2=WELL19937b._M3(12),  T6=WELL19937b._M3(4),
#  T3=WELL19937b._M3(-10), T7=WELL19937b._M3(-10))
#
#class WELL19937c(WELL19937a):
#    VERSION = u"WELL19937c ver0"
#
#WELL19937c._output_after_tempering(0xe46e1700, 0x9b868000)
#
#class WELL44497a(_WELLBase):
#    VERSION = u"WELL44497a ver0"
#    _K = 44497  # not use
#    _R = 1391
#    _P = 15
#
#WELL44497a.init_random_int32(
#  m1=23, m2=481, m3=229,
#  T0=WELL44497a._M3(-24), T4=WELL44497a._M1(),
#  T1=WELL44497a._M3(30),  T5=WELL44497a._M3(20),
#  T2=WELL44497a._M3(-10), T6=WELL44497a._M6(9, 0xb729fcec,
#                                            0xfbffffff, 0x00020000),
#  T3=WELL44497a._M2(-26), T7=WELL44497a._M1())
#
#class WELL44497b(WELL44497a):
#    VERSION = u"WELL44497b ver0"
#
#WELL44497b._output_after_tempering(0x93dd1400, 0xfa118000)
#
# EOF

イッシーの利用目的だと、WELL512aでおそらく十分ですので、
要望や助力がない限りは、これ以上の実装例は増やさない予定です。

英語読めなくても、論文とソースを見比べていけば、なんとななるはずです。
(イッシーがそうです)

あと、init_random_int32()の、me_maskは、高次最適化の為のオプションです。
これはそのうち対応したいなぁ。

0 件のコメント:

コメントを投稿