590 lines
15 KiB
Fortran
590 lines
15 KiB
Fortran
!!******************************************************************************
|
||
!!
|
||
!! This file is part of the AMUN source code, a program to perform
|
||
!! Newtonian or relativistic magnetohydrodynamical simulations on uniform or
|
||
!! adaptive mesh.
|
||
!!
|
||
!! Copyright (C) 2008-2020 Grzegorz Kowal <grzegorz@amuncode.org>
|
||
!!
|
||
!! This program is free software: you can redistribute it and/or modify
|
||
!! it under the terms of the GNU General Public License as published by
|
||
!! the Free Software Foundation, either version 3 of the License, or
|
||
!! (at your option) any later version.
|
||
!!
|
||
!! This program is distributed in the hope that it will be useful,
|
||
!! but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
!! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
!! GNU General Public License for more details.
|
||
!!
|
||
!! You should have received a copy of the GNU General Public License
|
||
!! along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
!!
|
||
!!******************************************************************************
|
||
!!
|
||
!! module: RANDOM
|
||
!!
|
||
!! This module provides subroutines to random number generators.
|
||
!!
|
||
!! References:
|
||
!!
|
||
!! [1] David Blackman and Sebastiano Vigna,
|
||
!! "Scrambled linear pseudorandom number generators", 2019
|
||
!! http://vigna.di.unimi.it/ftp/papers/ScrambledLinear.pdf
|
||
!! [2] http://prng.di.unimi.it/
|
||
!!
|
||
!!******************************************************************************
|
||
!
|
||
module random
|
||
|
||
#ifdef PROFILE
|
||
! include external subroutines
|
||
!
|
||
use timers, only : set_timer, start_timer, stop_timer
|
||
#endif /* PROFILE */
|
||
|
||
! declare all module variables as implicit
|
||
!
|
||
implicit none
|
||
|
||
#ifdef PROFILE
|
||
! timer indices
|
||
!
|
||
integer, save :: iri, irg
|
||
#endif /* PROFILE */
|
||
|
||
! random generator type
|
||
!
|
||
character(len = 32), save :: gentype = "same"
|
||
|
||
! variables to store seeds for random generator
|
||
!
|
||
integer , save :: kp = 0, np = 0
|
||
integer , save :: nseeds, lseed
|
||
integer , save :: state
|
||
integer(kind=8), dimension(:,:), allocatable, save :: seeds
|
||
integer(kind=4), dimension(:), allocatable, save :: ss
|
||
|
||
! integer to real conversion parameters
|
||
!
|
||
real(kind=8), parameter :: i64tor8 = 2.0d+00**(-53)
|
||
|
||
! by default everything is private
|
||
!
|
||
private
|
||
|
||
! declare public subroutines
|
||
!
|
||
public :: initialize_random, finalize_random
|
||
public :: nseeds, set_seeds, get_seeds, gentype
|
||
public :: randuni, randsym, randnorz
|
||
|
||
contains
|
||
!
|
||
!===============================================================================
|
||
!
|
||
! subroutine INITIALIZE_RANDOM:
|
||
! ----------------------------
|
||
!
|
||
! Subroutine initializes random number generator.
|
||
!
|
||
! Arguments:
|
||
!
|
||
! nthreads - the number of threads;
|
||
! nthread - the thread number;
|
||
! nproc - the process number;
|
||
!
|
||
!===============================================================================
|
||
!
|
||
subroutine initialize_random(nthreads, nthread, nproc, status)
|
||
|
||
use parameters, only : get_parameter
|
||
|
||
implicit none
|
||
|
||
! subroutine arguments
|
||
!
|
||
integer, intent(in) :: nthreads, nthread, nproc
|
||
integer, intent(out) :: status
|
||
|
||
! local variables
|
||
!
|
||
integer :: i
|
||
|
||
! local arrays
|
||
!
|
||
integer(kind=8), dimension(4) :: s
|
||
!
|
||
!-------------------------------------------------------------------------------
|
||
!
|
||
status = 0
|
||
|
||
#ifdef PROFILE
|
||
call set_timer('random:: initialization' , iri)
|
||
call set_timer('random:: number generation', irg)
|
||
|
||
call start_timer(iri)
|
||
#endif /* PROFILE */
|
||
|
||
! set local copies of the thread and process numbers,
|
||
! determine the number of seeds
|
||
!
|
||
kp = nthread
|
||
np = nproc
|
||
nseeds = nthreads
|
||
lseed = nseeds - 1
|
||
|
||
! allocate seeds
|
||
!
|
||
allocate(seeds(4, 0:lseed), stat = status)
|
||
|
||
if (status == 0) then
|
||
|
||
! prepare seeds depending on the type of generator
|
||
!
|
||
call get_parameter("gentype", gentype)
|
||
select case(gentype)
|
||
case('random')
|
||
state = 1234567890
|
||
s(1) = splitmix64()
|
||
s(2) = splitmix64()
|
||
s(3) = splitmix64()
|
||
s(4) = splitmix64()
|
||
do i = 1, nseeds * np
|
||
call jump(s(:))
|
||
end do
|
||
seeds(:,0) = s(:)
|
||
do i = 1, lseed
|
||
call jump(s(:))
|
||
seeds(:,i) = s(:)
|
||
end do
|
||
case default
|
||
state = 1234567890
|
||
seeds(1,:) = splitmix64()
|
||
seeds(2,:) = splitmix64()
|
||
seeds(3,:) = splitmix64()
|
||
seeds(4,:) = splitmix64()
|
||
end select
|
||
|
||
end if
|
||
|
||
#ifdef PROFILE
|
||
call stop_timer(iri)
|
||
#endif /* PROFILE */
|
||
|
||
!-------------------------------------------------------------------------------
|
||
!
|
||
end subroutine initialize_random
|
||
!
|
||
!===============================================================================
|
||
!
|
||
! subroutine FINALIZE_RANDOM:
|
||
! --------------------------
|
||
!
|
||
! Subroutine releases memory allocated by random number generator variables.
|
||
!
|
||
!===============================================================================
|
||
!
|
||
subroutine finalize_random()
|
||
|
||
implicit none
|
||
!
|
||
!-------------------------------------------------------------------------------
|
||
!
|
||
#ifdef PROFILE
|
||
call start_timer(iri)
|
||
#endif /* PROFILE */
|
||
|
||
if (allocated(seeds)) deallocate(seeds)
|
||
|
||
#ifdef PROFILE
|
||
call stop_timer(iri)
|
||
#endif /* PROFILE */
|
||
|
||
!-------------------------------------------------------------------------------
|
||
!
|
||
end subroutine finalize_random
|
||
!
|
||
!===============================================================================
|
||
!
|
||
! subroutine SET_SEEDS:
|
||
! --------------------
|
||
!
|
||
! Subroutine sets the seeds.
|
||
!
|
||
! Arguments:
|
||
!
|
||
! n - the number of seeds;
|
||
! sds - the new seeds;
|
||
! gen - the flag indication if the seeds have to be regenerated;
|
||
!
|
||
!===============================================================================
|
||
!
|
||
subroutine set_seeds(n, sds, gen)
|
||
|
||
implicit none
|
||
|
||
! input arguments
|
||
!
|
||
integer , intent(in) :: n
|
||
integer(kind=8), dimension(4,n), intent(in) :: sds
|
||
logical , intent(in) :: gen
|
||
|
||
! local variables
|
||
!
|
||
integer :: i
|
||
|
||
! local arrays
|
||
!
|
||
integer(kind=8), dimension(4) :: s
|
||
!
|
||
!-------------------------------------------------------------------------------
|
||
!
|
||
#ifdef PROFILE
|
||
call start_timer(iri)
|
||
#endif /* PROFILE */
|
||
|
||
! set the seeds only if the input array and seeds have the same sizes
|
||
!
|
||
if (gen .or. n /= nseeds) then
|
||
select case(gentype)
|
||
case('random')
|
||
state = 1234567890
|
||
s(1) = splitmix64()
|
||
s(2) = splitmix64()
|
||
s(3) = splitmix64()
|
||
s(4) = splitmix64()
|
||
do i = 1, nseeds * np
|
||
call jump(s(:))
|
||
end do
|
||
seeds(:,0) = s(:)
|
||
do i = 1, lseed
|
||
call jump(s(:))
|
||
seeds(:,i) = s(:)
|
||
end do
|
||
case default
|
||
state = 1234567890
|
||
seeds(1,:) = splitmix64()
|
||
seeds(2,:) = splitmix64()
|
||
seeds(3,:) = splitmix64()
|
||
seeds(4,:) = splitmix64()
|
||
end select
|
||
else
|
||
seeds(:,:) = sds(:,:)
|
||
end if
|
||
|
||
#ifdef PROFILE
|
||
call stop_timer(iri)
|
||
#endif /* PROFILE */
|
||
|
||
!-------------------------------------------------------------------------------
|
||
!
|
||
end subroutine set_seeds
|
||
!
|
||
!===============================================================================
|
||
!
|
||
! subroutine GET_SEEDS:
|
||
! --------------------
|
||
!
|
||
! Subroutine returns the seeds.
|
||
!
|
||
! Arguments:
|
||
!
|
||
! sds - the return seeds array;
|
||
!
|
||
!===============================================================================
|
||
!
|
||
subroutine get_seeds(sds)
|
||
|
||
implicit none
|
||
|
||
! output arguments
|
||
!
|
||
integer(kind=8), dimension(4,nseeds), intent(out) :: sds
|
||
!
|
||
!-------------------------------------------------------------------------------
|
||
!
|
||
#ifdef PROFILE
|
||
call start_timer(iri)
|
||
#endif /* PROFILE */
|
||
|
||
if (size(sds,1) == 4 .and. size(sds,2) == nseeds) then
|
||
sds(:,:) = seeds(:,:)
|
||
end if
|
||
|
||
#ifdef PROFILE
|
||
call stop_timer(iri)
|
||
#endif /* PROFILE */
|
||
|
||
!-------------------------------------------------------------------------------
|
||
!
|
||
end subroutine get_seeds
|
||
!
|
||
!===============================================================================
|
||
!
|
||
! function SPLITMIX64:
|
||
! -------------------
|
||
!
|
||
! Pseudorandom Number Generator for floating point numbers based on
|
||
! SplitMix64 algorithm [1], used for initialization of xoshiro256p()
|
||
! generator.
|
||
!
|
||
! References:
|
||
!
|
||
! [1] David Blackman and Sebastiano Vigna,
|
||
! "Scrambled linear pseudorandom number generators", 2019
|
||
! http://vigna.di.unimi.it/ftp/papers/ScrambledLinear.pdf
|
||
!
|
||
!===============================================================================
|
||
!
|
||
integer(kind=8) function splitmix64()
|
||
|
||
implicit none
|
||
|
||
! local parameters
|
||
!
|
||
integer(kind=8), parameter :: a = -7046029254386353131_8
|
||
integer(kind=8), parameter :: b = -4658895280553007687_8
|
||
integer(kind=8), parameter :: c = -7723592293110705685_8
|
||
!
|
||
!-------------------------------------------------------------------------------
|
||
!
|
||
state = state + a
|
||
splitmix64 = state
|
||
splitmix64 = ieor(splitmix64, ishft(splitmix64, -30)) * b
|
||
splitmix64 = ieor(splitmix64, ishft(splitmix64, -27)) * c
|
||
splitmix64 = ieor(splitmix64, ishft(splitmix64, -31))
|
||
|
||
return
|
||
|
||
!-------------------------------------------------------------------------------
|
||
!
|
||
end function splitmix64
|
||
!
|
||
!===============================================================================
|
||
!
|
||
! function XOSHIRO256P:
|
||
! --------------------
|
||
!
|
||
! Pseudorandom Number Generator for floating point numbers based on
|
||
! xoshiro256+ algorithm [1]. It return a 256-bit random integer.
|
||
!
|
||
! References:
|
||
!
|
||
! [1] David Blackman and Sebastiano Vigna,
|
||
! "Scrambled linear pseudorandom number generators", 2019
|
||
! http://vigna.di.unimi.it/ftp/papers/ScrambledLinear.pdf
|
||
!
|
||
!===============================================================================
|
||
!
|
||
integer(kind=8) function xoshiro256p()
|
||
|
||
implicit none
|
||
|
||
! local variables
|
||
!
|
||
integer(kind=8) :: temp
|
||
!
|
||
!-------------------------------------------------------------------------------
|
||
!
|
||
xoshiro256p = ishft(seeds(1,kp) + seeds(4,kp), -11)
|
||
temp = ishft(seeds(2,kp), 17)
|
||
seeds(3,kp) = ieor(seeds(3,kp), seeds(1,kp))
|
||
seeds(4,kp) = ieor(seeds(4,kp), seeds(2,kp))
|
||
seeds(2,kp) = ieor(seeds(2,kp), seeds(3,kp))
|
||
seeds(1,kp) = ieor(seeds(1,kp), seeds(4,kp))
|
||
seeds(3,kp) = ieor(seeds(3,kp), temp)
|
||
seeds(4,kp) = ior(ishft(seeds(4,kp), 45), ishft(seeds(4,kp), -19))
|
||
|
||
return
|
||
|
||
!-------------------------------------------------------------------------------
|
||
!
|
||
end function xoshiro256p
|
||
!
|
||
!===============================================================================
|
||
!
|
||
! subroutine JUMP:
|
||
! ---------------
|
||
!
|
||
! The jump function for the xoshiro256p() generator used to generate
|
||
! non-overlapping seed subsequences for parallel computations.
|
||
!
|
||
! Arguments:
|
||
!
|
||
! sds - temporary seeds array;
|
||
!
|
||
! References:
|
||
!
|
||
! [1] David Blackman and Sebastiano Vigna,
|
||
! "Scrambled linear pseudorandom number generators", 2019
|
||
! http://vigna.di.unimi.it/ftp/papers/ScrambledLinear.pdf
|
||
!
|
||
!===============================================================================
|
||
!
|
||
subroutine jump(sds)
|
||
|
||
implicit none
|
||
|
||
! arguments
|
||
!
|
||
integer(kind=8), dimension(4), intent(inout) :: sds
|
||
|
||
! local variables
|
||
!
|
||
integer(kind=8), dimension(4) :: s
|
||
integer(kind=8) :: temp
|
||
integer :: i, b
|
||
|
||
! local parameters
|
||
!
|
||
integer(kind=8), dimension(4), parameter :: jmp = (/ 1733541517147835066_8 &
|
||
, -3051731464161248980_8 &
|
||
, -6244198995065845334_8 &
|
||
, 4155657270789760540_8 /)
|
||
!
|
||
!-------------------------------------------------------------------------------
|
||
!
|
||
s(:) = 0_8
|
||
do i = 1, 4
|
||
do b = 1, 64
|
||
if (iand(jmp(i), ishft(1_8, b)) /= 0_8) then
|
||
s(1) = ieor(s(1), sds(1))
|
||
s(2) = ieor(s(2), sds(2))
|
||
s(3) = ieor(s(3), sds(3))
|
||
s(4) = ieor(s(4), sds(4))
|
||
end if
|
||
temp = ishft(sds(2), 17)
|
||
sds(3) = ieor(sds(3), sds(1))
|
||
sds(4) = ieor(sds(4), sds(2))
|
||
sds(2) = ieor(sds(2), sds(3))
|
||
sds(1) = ieor(sds(1), sds(4))
|
||
sds(3) = ieor(sds(3), temp)
|
||
sds(4) = ior(ishft(sds(4), 45), ishft(sds(4), -19))
|
||
end do
|
||
end do
|
||
sds(:) = s(:)
|
||
|
||
return
|
||
|
||
!-------------------------------------------------------------------------------
|
||
!
|
||
end subroutine jump
|
||
!
|
||
!===============================================================================
|
||
!
|
||
! function RANDUNI:
|
||
! ----------------
|
||
!
|
||
! Function generates uniformly distributed floating point real random number
|
||
! in range 0.0..1.0 using xoshiro256p() generator.
|
||
!
|
||
!===============================================================================
|
||
!
|
||
real(kind=8) function randuni()
|
||
|
||
implicit none
|
||
!
|
||
!-------------------------------------------------------------------------------
|
||
!
|
||
#ifdef PROFILE
|
||
call start_timer(irg)
|
||
#endif /* PROFILE */
|
||
|
||
randuni = i64tor8 * xoshiro256p()
|
||
|
||
#ifdef PROFILE
|
||
call stop_timer(irg)
|
||
#endif /* PROFILE */
|
||
|
||
return
|
||
|
||
!-------------------------------------------------------------------------------
|
||
!
|
||
end function randuni
|
||
!
|
||
!===============================================================================
|
||
!
|
||
! function RANDSYM:
|
||
! ----------------
|
||
!
|
||
! Function generates uniformly distributed floating point random number
|
||
! in range -1.0..1.0 using xoshiro256p() generator.
|
||
!
|
||
!===============================================================================
|
||
!
|
||
real(kind=8) function randsym()
|
||
|
||
implicit none
|
||
!
|
||
!-------------------------------------------------------------------------------
|
||
!
|
||
#ifdef PROFILE
|
||
call start_timer(irg)
|
||
#endif /* PROFILE */
|
||
|
||
randsym = 2.0d+00 * (i64tor8 * xoshiro256p() - 5.0d-01)
|
||
|
||
#ifdef PROFILE
|
||
call stop_timer(irg)
|
||
#endif /* PROFILE */
|
||
|
||
return
|
||
|
||
!-------------------------------------------------------------------------------
|
||
!
|
||
end function randsym
|
||
!
|
||
!===============================================================================
|
||
!
|
||
! function RANDNORZ:
|
||
! -----------------
|
||
!
|
||
! Function generates complex floating point number with a normal
|
||
! distribution (μ = 0, σ = 1) using Marsaglia polar method [1].
|
||
!
|
||
! References:
|
||
!
|
||
! [1] Marsaglia, G. & Bray, T. A.,
|
||
! "A Convenient Method for Generating Normal Variables",
|
||
! SIAM Review, vol. 6, no. 3, pp. 260-264,
|
||
! https://doi.org/10.1137/1006063
|
||
!
|
||
!===============================================================================
|
||
!
|
||
complex(kind=8) function randnorz()
|
||
|
||
implicit none
|
||
|
||
logical :: c
|
||
real(kind=8) :: u, v, s
|
||
!
|
||
!-------------------------------------------------------------------------------
|
||
!
|
||
#ifdef PROFILE
|
||
call start_timer(irg)
|
||
#endif /* PROFILE */
|
||
|
||
c = .true.
|
||
do while(c)
|
||
u = randsym()
|
||
v = randsym()
|
||
s = u**2 + v**2
|
||
c = s <= 0.0d+00 .or. s >= 1.0d+00
|
||
end do
|
||
|
||
randnorz = dsqrt(- 2.0d+00 * dlog(s) / s) * dcmplx(u, v)
|
||
|
||
#ifdef PROFILE
|
||
call stop_timer(irg)
|
||
#endif /* PROFILE */
|
||
|
||
return
|
||
|
||
!-------------------------------------------------------------------------------
|
||
!
|
||
end function randnorz
|
||
|
||
!===============================================================================
|
||
!
|
||
end module random
|