- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
I have an OpenMP paralelized loop. I would like to be able to replicate experiments everytime I run the program. The program calls a lot of random numbers. The issue is given the nature of parallelization I cannot replicate the experiments from one run to the next run. Is there anyway of doing this? I can see two options:
1) Call all the random numbers before executing the paralelized loop (I really would like to avoid this option as potentially the number of random numbers to be generated could be unknown at the time one enters the loop);
2) Have an option to fix the seeds for each thread. This is something that is possible in matlab very easily by setting a seed that depends on the index of the parallelized loop. E.g:
%% Matlab example
parfor k = 1:nsim rng(k, 'twister'); end
Below a minimal working example of something similar to what I am running:
%% Fortran minimal working example
program mwe
use tools
implicit none
integer :: ind1, ind2, ind3
integer :: n1 = 1000, n2 = 750, n3 = 300
real :: mu, mu1=0.01, mu2=0.02, sigma, sigma1=0.20, sigma2=0.40
real :: aux
real, allocatable :: z(:,:,:)
allocate(z(n1, n2, n3))
!$omp parallel do default(private) shared(n1, n2, n3, z, mu1, mu2, sigma1, sigma2)
do ind1 = 1,n1
do ind2=1,n2
do ind3 = 1,n3
call RANDOM_NUMBER(aux)
if (aux .le. 0.5) then
mu = mu1
sigma = sigma1
else
mu = mu2
sigma = sigma2
end if
z(ind1,ind2,ind3) = normal(mu,sigma)
end do
end do
end do
!$omp end parallel do
ind1 = 1
end program
MODULE tools
contains
function ran1() !returns random number between 0 - 1
implicit none
real :: ran1,x
call random_number(x) ! built in fortran 90 random number function
ran1=x
end function ran1
function normal(mean,sigma) !returns a normal distribution
implicit none
real :: normal,tmp
real :: mean,sigma
integer flag
real :: fac,gsave,rsq,r1,r2
save flag,gsave
data flag /0/
if (flag.eq.0) then
rsq=2.0
do while(rsq.ge.1.0.or.rsq.eq.0.0) ! new from for do
r1=2.0*ran1()-1.0
r2=2.0*ran1()-1.0
rsq=r1*r1+r2*r2
enddo
fac=sqrt(-2.0*log(rsq)/rsq)
gsave=r1*fac
tmp=r2*fac
flag=1
else
tmp=gsave
flag=0
endif
normal=tmp*sigma+mean
return
end function normal
end module
Link Copied
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
There is no support in RANDOM_NUMBER for thread-specific sequences. Fortran 2018 sort-of has this but only at the coarray image level.
Other than your first option, all I can think of is to provide your own RNG that allows you to pass the seed in, and have that seed be a thread-private (or FIRSTPRIVATE) variable.
How much do you care about the "quality" of the RNG? You could use one of the portability library routines that have seed arguments.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Steve Lionel (Ret.) wrote:
There is no support in RANDOM_NUMBER for thread-specific sequences. Fortran 2018 sort-of has this but only at the coarray image level.
Other than your first option, all I can think of is to provide your own RNG that allows you to pass the seed in, and have that seed be a thread-private (or FIRSTPRIVATE) variable.
How much do you care about the "quality" of the RNG? You could use one of the portability library routines that have seed arguments.
I do not really care about "quality" of the RNG (unless there is something that somehow bias the random numbers and consequently the overall results). Other than that I am happy to use any routine that generates random numbers. If they are thread safe and allow me to replicate results even with OpenMP perfect.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Check the Intel Visual Fortran document via index for RANDOM_NUMBER. You will find an example using RANDOM_SEED and RANDOM_NUMBER. You can modify this such that you can:
a) create a once-only parallel region that determines the number of threads
b) allocate a seed array the size required for the number of threads (zero based)
c) populate the seed array with appropriate seed values, one per thread
d) in you main parallel loop (do ind1=1,n1) inner most loop, use a critical section that PUT's the seed value for the thread, obtains a harvest, then GET's the updated seed value before exiting the critical section.
Note, you may want to have each thread obtain a harvest of n3 number of random numbers. IOW place the d) acquisition between the do ind2 and do ind3 to collect n3 number of random numbers and thus reduce the number of critical sections from n1*n2*n3 to n1*n2.
Jim Dempsey
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Jim, thank you for the answer. I will give that a go and see if I can make it work.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
The problem with Jim's suggestion, though it will work, is that it introduces a critical section that blocks any other thread from getting a random number until the first one exits. This is why I suggested a simpler one that doesn't have global state the way RANDOM_NUMBER does.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
I modified Marsaglia & Tsang's ziggurat RNG to work with OpenMP. One initializes a collection of npar RNGs, and calls the various RV-returning functions with the argument kpar = 0,..,npar-1. I've been using this for several years now, and it seems to work well.
!Marsaglia, G. & Tsang, W.W. (2000) `The ziggurat method for generating random variables', J. Statist. Software, v5(8).
I'd be happy to send you the code if you wish.
Gib
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
>>The problem with Jim's suggestion, though it will work, is that it introduces a critical section that blocks any other thread from getting a random number until the first one exits.
And this is why I suggested you get batches of random numbers as opposed to one at a time.
Gib's suggestion of using a random number generator that is passed in and out the seed value is the better choice. Each thread though must be careful enough to obtain an initial (and different) seed value for use on each call.
Jim Dempsey
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
gib wrote:
I modified Marsaglia & Tsang's ziggurat RNG to work with OpenMP. One initializes a collection of npar RNGs, and calls the various RV-returning functions with the argument kpar = 0,..,npar-1. I've been using this for several years now, and it seems to work well.
!Marsaglia, G. & Tsang, W.W. (2000) `The ziggurat method for generating random variables', J. Statist. Software, v5(8).
I'd be happy to send you the code if you wish.
Gib
Hi Gib,
It would be great if you could share the code. Thank you very much.
Peter
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
This is how the generator is initialised (in a module that USEs Par_Zig_mod):
subroutine RngInitialisation(npar, seed)
integer :: npar, seed
integer, allocatable :: zig_seed(:)
integer :: i
integer :: grainsize = 32
allocate(zig_seed(0:npar-1))
do i = 0,npar-1
zig_seed(i) = seed*(i+1)
enddo
call par_zigset(npar,zig_seed,grainsize)
par_zig_init = .true.
end subroutine
This is the module:
! Marsaglia & Tsang generator for random normals & random exponentials. ! Translated from C by Alan Miller (amiller@bigpond.net.au) ! Marsaglia, G. & Tsang, W.W. (2000) `The ziggurat method for generating ! random variables', J. Statist. Software, v5(8). ! This is an electronic journal which can be downloaded from: ! http://www.jstatsoft.org/v05/i08 ! N.B. It is assumed that all integers are 32-bit. ! N.B. The value of M2 has been halved to compensate for the lack of ! unsigned integers in Fortran. ! Latest version - 1 January 2001 ! Parallel version - October 2006 ! This version has been customised for parallel processing use, ! specifically with OpenMP. Each thread uses its own pseudo-random ! sequence. (Gib Bogle) ! Note: thread index kpar is 0-based !-------------------------------------------------------------------------- MODULE Par_Zig_mod IMPLICIT NONE PRIVATE INTEGER, PARAMETER :: DP=SELECTED_REAL_KIND( 12, 60 ) REAL(DP), PARAMETER :: m1=2147483648.0_DP, m2=2147483648.0_DP, & half=0.5_DP REAL(DP) :: dn0=3.442619855899_DP, tn0=3.442619855899_DP, & vn=0.00991256303526217_DP, & q, de0=7.697117470131487_DP, & te0=7.697117470131487_DP, & ve=0.003949659822581572_DP ! INTEGER, SAVE :: iz, jz, jsr=123456789, kn(0:127), & ! ke(0:255), hz ! REAL(DP), SAVE :: wn(0:127), fn(0:127), we(0:255), fe(0:255) ! LOGICAL, SAVE :: initialized=.FALSE. integer, save :: par_n = 0, par_step integer, allocatable, save :: par_jsr(:), par_kn(:,:), par_ke(:,:) real(DP), allocatable, save :: par_wn(:,:), par_fn(:,:), par_we(:,:), par_fe(:,:) PUBLIC :: par_zigset, par_zigfree, par_shr3, par_uni, par_rnor, par_rexp, par_test CONTAINS !-------------------------------------------------------------------------- !-------------------------------------------------------------------------- SUBROUTINE par_zigset( npar, par_jsrseed, grainsize) INTEGER, INTENT(IN) :: npar, grainsize, par_jsrseed(0:*) INTEGER :: i, kpar REAL(DP) :: dn, tn, de, te par_n = npar par_step = grainsize ! First we need to allocate all the non-volatile arrays with the size npar allocate(par_jsr(0:npar*par_step)) allocate(par_kn(0:127,0:npar-1)) allocate(par_ke(0:255,0:npar-1)) allocate(par_wn(0:127,0:npar-1)) allocate(par_fn(0:127,0:npar-1)) allocate(par_we(0:255,0:npar-1)) allocate(par_fe(0:255,0:npar-1)) ! Now treat each instance separately do kpar = 0,npar-1 ! Set the seed par_jsr(kpar*par_step) = par_jsrseed(kpar) ! Tables for RNOR dn = dn0 tn = tn0 q = vn*EXP(half*dn*dn) par_kn(0,kpar) = (dn/q)*m1 par_kn(1,kpar) = 0 par_wn(0,kpar) = q/m1 par_wn(127,kpar) = dn/m1 par_fn(0,kpar) = 1.0_DP par_fn(127,kpar) = EXP( -half*dn*dn ) DO i = 126, 1, -1 dn = SQRT( -2.0_DP * LOG( vn/dn + EXP( -half*dn*dn ) ) ) ! dn par_kn(i+1,kpar) = (dn/tn)*m1 tn = dn ! tn par_fn(i,kpar) = EXP(-half*dn*dn) par_wn(i,kpar) = dn/m1 END DO ! Tables for REXP de = de0 te = te0 q = ve*EXP( de ) par_ke(0,kpar) = (de/q)*m2 par_ke(1,kpar) = 0 par_we(0,kpar) = q/m2 par_we(255,kpar) = de/m2 par_fe(0,kpar) = 1.0_DP par_fe(255,kpar) = EXP( -de ) DO i = 254, 1, -1 de = -LOG( ve/de + EXP( -de ) ) ! de par_ke(i+1,kpar) = m2 * (de/te) te = de ! te par_fe(i,kpar) = EXP( -de ) par_we(i,kpar) = de/m2 END DO enddo RETURN END SUBROUTINE par_zigset !-------------------------------------------------------------------------- !-------------------------------------------------------------------------- subroutine par_zigfree deallocate(par_jsr) deallocate(par_kn) deallocate(par_ke) deallocate(par_wn) deallocate(par_fn) deallocate(par_we) deallocate(par_fe) end subroutine !-------------------------------------------------------------------------- ! Generate random 32-bit integers !-------------------------------------------------------------------------- FUNCTION par_shr3(kpar) RESULT( ival ) INTEGER :: ival, kpar integer :: jz, jsr jsr = par_jsr(kpar*par_step) jz = jsr jsr = IEOR( jsr, ISHFT( jsr, 13 ) ) jsr = IEOR( jsr, ISHFT( jsr, -17 ) ) jsr = IEOR( jsr, ISHFT( jsr, 5 ) ) par_jsr(kpar*par_step) = jsr ival = jz + jsr RETURN END FUNCTION par_shr3 !-------------------------------------------------------------------------- ! Generate uniformly distributed random numbers, sequence kpar !-------------------------------------------------------------------------- FUNCTION par_uni(kpar) RESULT( fn_val ) integer :: kpar REAL(DP) :: fn_val if (kpar >= par_n) then write(*,*) 'thread number: ',kpar,' exceeds initialized max: ',par_n-1 write(21,*) 'thread number: ',kpar,' exceeds initialized max: ',par_n-1 stop endif fn_val = half + 0.2328306e-9_DP * par_shr3(kpar) RETURN END FUNCTION par_uni FUNCTION par_test(kpar) RESULT( fn_val ) integer :: kpar integer :: fn_val if (kpar >= par_n) then write(*,*) 'thread number: ',kpar,' exceeds initialized max: ',par_n-1 stop endif fn_val = kpar return end function !-------------------------------------------------------------------------- ! Generate random normals, sequence kpar !-------------------------------------------------------------------------- FUNCTION par_rnor(kpar) RESULT( fn_val ) REAL(DP) :: fn_val integer :: kpar REAL(DP), PARAMETER :: r = 3.442620_DP REAL(DP) :: x, y integer :: iz, hz ! IF( .NOT. initialized ) CALL zigset( jsr ) if (kpar >= par_n) then write(*,*) 'thread number exceeds initialized max: ',kpar,par_n-1 stop endif hz = par_shr3(kpar) iz = IAND( hz, 127 ) IF( ABS( hz ) < par_kn(iz,kpar) ) THEN fn_val = hz * par_wn(iz,kpar) ELSE DO IF( iz == 0 ) THEN DO x = -0.2904764_DP* LOG( par_uni(kpar) ) y = -LOG( par_uni(kpar) ) IF( y+y >= x*x ) EXIT END DO fn_val = r+x IF( hz <= 0 ) fn_val = -fn_val RETURN END IF x = hz * par_wn(iz,kpar) IF( par_fn(iz,kpar) + par_uni(kpar)*(par_fn(iz-1,kpar)-par_fn(iz,kpar)) < EXP(-half*x*x) ) THEN fn_val = x RETURN END IF hz = par_shr3(kpar) iz = IAND( hz, 127 ) IF( ABS( hz ) < par_kn(iz,kpar) ) THEN fn_val = hz * par_wn(iz,kpar) RETURN END IF END DO END IF RETURN END FUNCTION par_rnor !-------------------------------------------------------------------------- ! Generate random exponentials, sequence kpar !-------------------------------------------------------------------------- FUNCTION par_rexp(kpar) RESULT( fn_val ) REAL(DP) :: fn_val integer :: kpar REAL(DP) :: x integer :: iz, jz ! IF( .NOT. initialized ) CALL Zigset( jsr ) if (kpar >= par_n) then write(*,*) 'thread number exceeds initialized max: ',kpar,par_n-1 stop endif jz = par_shr3(kpar) iz = IAND( jz, 255 ) IF( ABS( jz ) < par_ke(iz,kpar) ) THEN fn_val = ABS(jz) * par_we(iz,kpar) RETURN END IF DO IF( iz == 0 ) THEN fn_val = 7.69711 - LOG(par_uni(kpar) ) RETURN END IF x = ABS( jz ) * par_we(iz,kpar) IF( par_fe(iz,kpar) + par_uni(kpar)*(par_fe(iz-1,kpar) - par_fe(iz,kpar)) < EXP( -x ) ) THEN fn_val = x RETURN END IF jz = par_shr3(kpar) iz = IAND( jz, 255 ) IF( ABS( jz ) < par_ke(iz,kpar) ) THEN fn_val = ABS( jz ) * par_we(iz,kpar) RETURN END IF END DO RETURN END FUNCTION par_rexp END MODULE par_zig_mod
- Subscribe to RSS Feed
- Mark Topic as New
- Mark Topic as Read
- Float this Topic for Current User
- Bookmark
- Subscribe
- Printer Friendly Page