m_random.f90 Source File


Source Code

module m_random

  implicit none
  private
  public :: t_random


  !> Derived type to hold a random number generator, which does not interfere with the
  !! global `RANDOM_NUMBER()` routine of fortran.
  !!
  !! Using `t_random` can be useful when coupling software which depend on specifically
  !! seeded random number generator for internal use, and should not clash with another.
  !!
  type t_random
     integer, public :: zseed !! value of `zseed` at which this instance of `t_random` is initialized.
     integer, allocatable, private :: my_state(:), other_state(:)
   contains
     procedure, public :: reseed => t_random_reseed
     procedure, public :: random_number => t_random_number
     final :: t_random_destroy
  end type t_random

  interface t_random
     procedure t_random_initialize
  end interface t_random

contains


  function t_random_initialize( zseed )result(self)
    !! Create the `t_random` object with a seed value `zseed`. If `zseed=0`, a new seed is generated
    !! based on `system_clock` values.
    !!
    !! Call as:
    !!
    !!```f90
    !! type( t_random ), pointer :: rnd
    !! integer :: zseed
    !!
    !! ! with specified value for zseed:
    !! rnd => t_random( zseed )
    !!
    !! ! or without zseed (this is equivalent to zseed=0)
    !! rnd => t_random()
    !!```
    implicit none
    integer, intent(in), optional :: zseed !! if not present, new seed is generated
    type( t_random ), pointer :: self
    integer :: zs
    zs = 0
    if(present(zseed))zs=zseed
    allocate(t_random::self)
    call t_random_reseed( self, zs )
  end function t_random_initialize


  subroutine t_random_destroy( self )
    implicit none
    type( t_random ), intent(inout) :: self
    if( allocated(self%my_state)   ) deallocate( self%my_state )
    if( allocated(self%other_state)) deallocate( self%other_state )
  end subroutine t_random_destroy


  subroutine t_random_number( self, z )
    !! Obtain a random real number `z`, on range [0:1].
    implicit none
    class( t_random ), intent(inout) :: self
    real, intent(out) :: z(..) !! random[0:1], up to rank-3
    ! save external state
    call random_seed(get=self%other_state)
    ! put my state
    call random_seed(put=self%my_state)
    ! generate number
    select rank(z)
    rank(0); call random_number(z)
    rank(1); call random_number(z)
    rank(2); call random_number(z)
    rank(3); call random_number(z)
    end select
    ! save my new state
    call random_seed(get=self%my_state)
    ! re-put old external state
    call random_seed(put=self%other_state)
  end subroutine t_random_number


  subroutine t_random_reseed( self, zseed )
    !! re-seed the `t_random` object with a specific value of `zseed`.
    !!
    !! initialize random number generator, modified from:
    !! https://gcc.gnu.org/onlinedocs/gcc-4.9.1/gfortran/RANDOM_005fSEED.html
    !!
    !! Seed the random number generator with sequence generated from zseed.
    !! If on input `zseed = 0` then a new, repeatable seed sequence is generated.
    !! On output, `zseed` has value of actual seed used (single value), which can be
    !! used to reproduce the actual sequence of seed elements.
    use, intrinsic :: iso_fortran_env, only: int64
    implicit none
    class( t_random ), intent(inout) :: self
    integer, intent(inout) :: zseed
    integer :: n, i
    integer(int64) :: t

    if( zseed == 0 ) then
       ! generate seed from system clock
       call system_clock(t)
       t = mod( t, int(huge(0), int64) )
       zseed = int( t )
    end if
    ! save the generated zseed
    self%zseed = zseed

    call random_seed(size=n)
    if( allocated(self%other_state) .and. size(self%other_state) /= n)deallocate(self%other_state)
    if(.not.allocated(self%other_state))allocate(self%other_state(1:n))
    if( allocated(self%my_state) .and. size(self%my_state) /= n)deallocate(self%my_state)
    if( .not.allocated(self%my_state))allocate(self%my_state(1:n))

    ! put first element of seed
    self%my_state(1) = lcg( int(zseed, int64) )
    do i = 2, n
       ! other elements of seed are function of preceding seed element
       self%my_state(i) = lcg( int(self%my_state(i-1), int64) )
    end do

  contains
    ! This simple PRNG might not be good enough for real work, but is
    ! sufficient for seeding a better PRNG.
    function lcg(s)
      integer :: lcg
      integer(int64) :: s
      if (s == 0) then
         s = 104729
      else
         s = mod(s, 4294967296_int64)
      end if
      s = mod(s * 279470273_int64, 4294967291_int64)
      lcg = int(mod(s, int(huge(0), int64)), kind(0))
    end function lcg

  end subroutine t_random_reseed


end module m_random