匿名希望のおでんFortranツヴァイさん太郎

生き物、Fortran、川について書く

mt19937ar の一部を Modern Fortran で書いてみた

統計計算を行うのに何かと乱数が必要になります。Fortran には組み込み関数として random_number() がありますが、
コンパイラによって実装が異なります*1*2。環境によって品質が変わると困るので、自分で実装したものを使うのが安全だと思います。私はメルセンヌ ツイスターを用いています。これまでは mt19937 をモジュールとして実装していましたが、この度 mt19937ar をクラスにしてみました。記事の表題に Modern Fortran とあるのはモジュールやクラスを使ったから、くらいの意味です*3

以下に mt19937ar.c を移植したものを示します*4。都合により、mt19937ar.c にある init_by_array() および genrand_int31() は実装していません。また、関数の名称を変えています。

コメントはできるだけ付けました。しかしながら、私がメルセンヌ ツイスターの数学的な部分を理解していないので、あまり気の利いたコメントが書けませんでした。コメントの書式は Javadoc 風ですが、ドキュメント生成ツールを使うためではなく、変数の説明を書くのにちょうどよい書式だと思ったからです。

実装ミスをはじめ、間違い等ございましたら、ご指摘くださいませ。

! mt19937ar.c を Fortran へ移植したプログラムです。
! ただし、init_by_array() および genrand_int31() は実装していません。
! 
! [0.0, 1.0]、[0.0, 1.0)、(0.0, 1.0) からの一様乱数を生成します。
!
! 元のプログラムは以下のリンク先にあります。
! http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/MT2002/CODES/mt19937ar.c
!
!
! ----- 移植元ライセンス ここから----- 
!/* 
!   A C-program for MT19937, with initialization improved 2002/1/26.
!   Coded by Takuji Nishimura and Makoto Matsumoto.
!
!   Before using, initialize the state by using init_genrand(seed)  
!   or init_by_array(init_key, key_length).
!
!   Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura,
!   All rights reserved.                          
!
!   Redistribution and use in source and binary forms, with or without
!   modification, are permitted provided that the following conditions
!   are met:
!
!     1. Redistributions of source code must retain the above copyright
!        notice, this list of conditions and the following disclaimer.
!
!     2. Redistributions in binary form must reproduce the above copyright
!        notice, this list of conditions and the following disclaimer in the
!        documentation and/or other materials provided with the distribution.
!
!     3. The names of its contributors may not be used to endorse or promote 
!        products derived from this software without specific prior written 
!        permission.
!
!   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
!   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
!   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
!   A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
!   CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
!   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
!   PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
!   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
!   LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
!   NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
!   SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
!
!
!   Any feedback is very welcome.
!   http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html
!   email: m-mat @ math.sci.hiroshima-u.ac.jp (remove space)
!*/
! ----- 移植元ライセンス ここまで----- 
!
module Class_mt19937ar
    use, intrinsic :: iso_fortran_env
    implicit none

    private

    !  パラメータ
    integer(int32), parameter :: n = 624
    integer(int32), parameter :: m = 397
    ! 計算に使うために定義したパラメータです。
    integer(int32), parameter :: n_1 = n - 1
    integer(int32), parameter :: n_2 = n - 2
    integer(int32), parameter :: m_1 = m - 1
    integer(int32), parameter :: n_m_1 = n - m - 1
    integer(int32), parameter :: n_m = n - m
    integer(int32), parameter :: m_n = m - n

    ! 2147483647 は 0x7fffffff の符号付き 10 進表記です。
    integer(int32), parameter :: lowerMask = 2147483647

    ! 0x80000000 の符号付き 10 進表記です。
    integer(int32), parameter :: upperMask = -lowerMask - 1


    ! mt19937 による一様乱数生成器です。
    type, public :: mt19937ar
        integer(int32), private :: mt(0:n-1)
        integer(int32), private :: mti

        contains

        ! [0, 1] から 32 bit 精度の一様乱数を生成します。
        procedure, pass :: generateOnCC

        ! [0, 1) から 32 bit 精度の一様乱数を生成します。
        procedure, pass :: generateOnCO

        ! (0, 1) から 32 bit 精度の一様乱数を生成します。
        procedure, pass :: generateOnOO

        ! [0, 1) から 53 bit 精度の一様乱数を生成します。
        procedure, pass :: generateOnCO_53bit
    end type

 
    ! コンストラクタです。
    interface new_mt19937ar
      module procedure :: new_mt19937ar
    end interface


    ! public にする関数
    public new_mt19937ar
     

    contains


    ! コンストラクタです。シード値を指定して乱数生成器を初期化します。
    !
    ! @param inutSeed 乱数シード
    ! @return 乱数生成器
    function new_mt19937ar(inputSeed) result(newGenerator)
        ! 入力
        integer(int32), optional, intent(in) :: inputSeed

        ! 変数
        integer(int32) seed

        ! 戻り値
        type(mt19937ar) newGenerator


        ! シード値を決定します。
        if(present(inputSeed)) then
            ! 指定されている場合は、指定の値をシード値とします。
            seed = inputSeed
        else
            ! 指定されていない場合はデフォルト値 5489 を用います。
            seed = 5489
        end if

        ! -1 は 0xffffffffUL の符号あり 10 進表記です。
        newGenerator%mt(0) = iand(seed, -1)

        block 
            ! ループ カウンター
            integer(int32) i

            do i = 1, n_1
                newGenerator%mt(i) = 1812433253 &
                  & * (ieor(newGenerator%mt(i-1), ishft(newGenerator%mt(i-1), -30))) &
                  & + i

                ! -1 は 0xffffffffUL の符号あり 10 進表記です。
                newGenerator%mt(i) = iand(newGenerator%mt(i), -1);
            end do
            newGenerator%mti = n
        end block
    end function


    ! [0x0, 0xffffffff] の範囲から符号あり整数の乱数を返します。
    ! 
    ! @param this 乱数生成器
    ! @return [0x0, 0xffffffff] の範囲からの符号あり整数の乱数
    function generate_int32(this) result(y)
        ! 引数
        class(mt19937ar), intent(inout) :: this

        ! 戻り値
        integer(int32) y


        if (this%mti >= n) then
            call generateNWordsAtOneTime(this, y)
        end if

        y = this%mt(this%mti)
        this%mti = this%mti + 1

        ! Tempering
        ! ishft(i, j) で j =  n とすると左に n bit シフトします。
        ! ishft(i, j) で j = -n とすると右に n bit シフトします。
        y = ieor(y, ishft(y, -11))
        ! -1658038656 は 9d2c5680 の符号あり 10 進表記です。
        y = ieor(y, iand(ishft(y, 7), -1658038656))
        ! -272236544 は efc60000 の符号あり 10 進表記です。
        y = ieor(y, iand(ishft(y, 15), -272236544))
        y = ieor(y, ishft(y, -18))
    end function


    ! mti >= n の時に、内部状態を更新します。
    ! 
    ! @param this 乱数生成器
    ! @param y 符号あり整数
    subroutine generateNWordsAtOneTime(this, y)
        ! 引数
        class(mt19937ar), intent(inout) :: this
        integer(int32), intent(inout) :: y

        ! パラメータ
        ! -1727483681 は 9908b0df の符号なし 10 進表記です。
        integer(int32), parameter :: matrixA = -1727483681
        integer(int32), parameter :: mag01(0:1) = (/0, matrixA/)


        ! ループ カウンター
        integer(int32) kk

        do kk = 0, n_m_1
            y = ior(iand(this%mt(kk)  , upperMask), &
                  & iand(this%mt(kk+1), lowerMask))

            this%mt(kk) = ieor(ieor(this%mt(kk+m), ishft(y, -1)), &
                             & mag01(iand(y, 1)))
        end do

        do kk = n_m, n_2
            y = ior(iand(this%mt(kk)  , upperMask), &
                  & iand(this%mt(kk+1), lowerMask))

            this%mt(kk) = ieor(ieor(this%mt(kk+m_n), ishft(y, -1)), &
                             & mag01(iand(y, 1)))
        end do

        y = ior(iand(this%mt(n_1), upperMask), iand(this%mt(0), lowerMask))
        this%mt(n_1) = ieor(ieor(this%mt(m_1), ishft(y, -1)), &
                          & mag01(iand(y, 1)))

        this%mti = 0
    end subroutine 


    ! 以下、[0, 1]、[0, 1)、(0, 1) から一様乱数を発生させる関数です。
    ! 関数名の on の後につく O は 端点を含まないことを (open の意味) 、
    ! C は端点を含むことを (close の意味) 表します。
    !
    ! 例えば、
    ! generateOnCO --- [0, 1) からの一様乱数
    ! generateOnOO --- (0, 1) からの一様乱数
    ! を表します。


    ! [0, 1] から 32 bit 精度の一様乱数を発生させます。
    !
    ! @param this 乱数生成器
    ! @return [0, 1] からの一様乱数
    function generateOnCC(this) result(outputRandomNumber)
        ! 引数
        class(mt19937ar), intent(inout) :: this

        ! 変数
        integer(int32) intRandomNumber
        real(real64) convertedIntRandomNumber

        ! 戻り値
        real(real64) outputRandomNumber


        ! 符号ありの整数乱数を取得します。
        intRandomNumber = generate_int32(this)

        ! 符号なし整数としての値を、実数として取得します。
        convertedIntRandomNumber & 
          & = convertIntegerToUnsignedIntegerAsReal(intRandomNumber)

        ! [0, 1] の値に変換します。
        ! 4294967295 は 2^32-1 です。
        outputRandomNumber = convertedIntRandomNumber / 4294967295.0d0
    end function


    ! [0, 1) から 32 bit 精度の一様乱数を発生させます。
    !
    ! @param this 乱数生成器
    ! @return [0, 1) からの一様乱数
    function generateOnCO(this) result(outputRandomNumber)
        ! 引数
        class(mt19937ar), intent(inout) :: this

        ! 変数
        integer(int32) intRandomNumber
        real(real64) convertedIntRandomNumber

        ! 戻り値
        real(real64) outputRandomNumber


        ! 符号ありの整数乱数を取得します。
        intRandomNumber = generate_int32(this)

        ! 符号なし整数としての値を、実数として取得します。
        convertedIntRandomNumber & 
          & = convertIntegerToUnsignedIntegerAsReal(intRandomNumber)

        ! [0, 1) の値に変換します。
        ! 4294967295 は 2^32 です。
        outputRandomNumber = convertedIntRandomNumber / 4294967296.0d0
    end function


    ! [0, 1) から 53 bit 精度の一様乱数を発生させます。
    !
    ! @param this 乱数生成器
    ! @return [0, 1) からの一様乱数
    function generateOnCO_53bit(this) result(outputRandomNumber)
        ! 引数
        class(mt19937ar), intent(inout) :: this

        ! 変数
        integer(int32) a_int
        integer(int32) b_int
        real(real64) a_double
        real(real64) b_double

        ! 戻り値
        real(real64) outputRandomNumber


        ! 符号ありの整数乱数を取得します。
        a_int = ishft(generate_int32(this), -5)
        b_int = ishft(generate_int32(this), -6)
       
        ! 符号なし整数としての値を、実数として取得します。
        a_double = convertIntegerToUnsignedIntegerAsReal(a_int)
        b_double = convertIntegerToUnsignedIntegerAsReal(b_int)

        ! [0, 1) の値に変換します。 
        ! 4294967295 は 2^32 です。
        outputRandomNumber = (a_double * 67108864.0 + b_double) &
                             & / 9007199254740992.0d0
    end function


    ! (0, 1) から 32 bit 精度の一様乱数を発生させます。
    !
    ! @param this 乱数生成器
    ! @return (0, 1) からの一様乱数
    function generateOnOO(this) result(outputRandomNumber)
        ! 引数
        class(mt19937ar), intent(inout) :: this

        ! 変数
        integer(int32) intRandomNumber
        real(real64) convertedIntRandomNumber

        ! 戻り値
        real(real64) outputRandomNumber


        ! 符号ありの整数乱数を取得します。
        intRandomNumber = generate_int32(this)

        ! 符号なし整数としての値を、実数として取得します。
        convertedIntRandomNumber = &
          & convertIntegerToUnsignedIntegerAsReal(intRandomNumber)

        ! (0, 1) の値に変換します。 
        ! 4294967295 は 2^32 です。
        outputRandomNumber = (convertedIntRandomNumber + 0.5d0) &
                             & / 4294967296.0d0
    end function


    ! 符号あり整数を、符号なし整数を倍精度実数に変換した値にします。
    ! Fortran には unsigned long がないため、この関数で unsigned long
    ! としての値に変換します。
    ! 
    ! @param inputInteger 整数
    ! @return 符号あり整数を倍精度実数に変換した値
    function convertIntegerToUnsignedIntegerAsReal(inputInteger) result(outputReal)
        ! 引数
        integer(int32), intent(in) :: inputInteger

        ! 戻り値
        real(real64) outputReal


        if (inputInteger < 0) then
            ! 4294967296 は 2^32 です。
            outputReal = dble(inputInteger) + 4294967296.d0
        else
            outputReal = dble(inputInteger)
        end if 
    end function
end module


! メイン関数です。
! 使用例として、乱数生成器から 4 種類の乱数を生成します。
program MainProgram
    use, intrinsic :: iso_fortran_env
    use Class_mt19937ar
    implicit none

    ! 変数
    ! 乱数生成器
    type(mt19937ar) generator
    integer(int32) file_randomNumber

    ! ループ カウンター
    integer(int32) i


    ! 乱数生成器を生成します。
    generator = new_mt19937ar(5489)
    open(newunit=file_randomNumber, file='fortran_mt19937ar_CC.csv', status='replace')
    ! 乱数をファイルに出力します。
    do i = 1, 10000
        write(file_randomNumber, *) generator%generateOnCC() 
    end do
    close(file_randomNumber)

    ! 乱数生成器のシードを再設定します。
    generator = new_mt19937ar(5489)
    open(newunit=file_randomNumber, file='fortran_mt19937ar_CO.csv', status='replace')
    ! 乱数をファイルに出力します。
    do i = 1, 10000
        write(file_randomNumber, *) generator%generateOnCO() 
    end do
    close(file_randomNumber)

    ! 乱数生成器のシードを再設定します。
    generator = new_mt19937ar(5489)
    open(newunit=file_randomNumber, file='fortran_mt19937ar_OO.csv', status='replace')
    ! 乱数をファイルに出力します。
    do i = 1, 10000
        write(file_randomNumber, *) generator%generateOnOO() 
    end do
    close(file_randomNumber)

    ! 乱数生成器のシードを再設定します。
    generator= new_mt19937ar(5489)
    open(newunit=file_randomNumber, file='fortran_mt19937ar_CO_53bit.csv', status='replace')
    ! 乱数をファイルに出力します。
    do i = 1, 10000
        write(file_randomNumber, *) generator%generateOnCO_53bit() 
    end do
    close(file_randomNumber)
end program

*1:例えば、GCC 6.5 では KISS algorithm ですが、GCC 7.4 では xorshift1024* になっています。 gcc.gnu.org gcc.gnu.org

*2:様々な乱数を信頼できるアルゴリズムで実装した標準ライブラリを提供していないのは、 科学技術計算用言語として致命的だと思います。

*3:Modern Fortran の厳密な定義は知りません。

*4:mt19937ar の説明は以下にあります。同リンク先の mt19937ar.c が移植元のプログラムです。 www.math.sci.hiroshima-u.ac.jp