fortran - 随机数生成器(RNG/PRNG)返回种子的更新值

标签 fortran gfortran fortran90 intel-fortran

我正在尝试编写一个RNG,它还返回更新后的种子的值。这样做的可能显而易见的原因是,以后可以在不更改现有RNG值的情况下将新的随机变量添加到程序中。有关此问题的python / numpy版本,请参见例如:Difference between RandomState and seed in numpy

这是(临时)建议的解决方案的用法示例:

program main

   ! note that I am assuming size=12 for the random 
   ! seed but this is implementation specific and
   ! could be different in your implementation
   ! (you can query this with random_seed(size) btw)

   integer :: iseed1(12) = 123456789
   integer :: iseed2(12) = 987654321

   do i = 1, 2
      x = ran(iseed1)
      y = ran(iseed2)
   end do

end program main

function ran( seed )

   integer, intent(inout) :: seed(12)
   real                   :: ran

   call random_seed( put=seed )
   call random_number( ran )
   call random_seed( get=seed )

end function ran


请注意,此解决方案(以及任何其他解决方案)的重要方面是,如果我们在上面添加了seed3x3,则x1x2的实现值将保持不变。同样,可以从代码中删除x1x2而不影响另一个值。

如果有帮助,这ran()(扩展)功能如何在Compaq / HP和Intel编译器上起作用,我基本上是在尝试在gfortran上实现相同的API。但是请注意,该函数的种子是标量,而它是使用Fortran 90子例程random_seed的一维数组(数组的大小/长度是实现特定的)。

我正在提供当前的解决方案作为基准错误,但希望其他人可以批评该答案或提供更好的答案。

我希望您能根据标准PRNG测试对基准结果进行任何分析,尤其是对种子的种植方式进行分析。在基准测试中,我仅使用标量广播到一个非常简单明了的数组,并希望避免显式提供整个整数数组。

因此,我想或者稍微严格地确认这是好的,还是要比写出整个数组(例如12或33个整数)更简洁的方法来设置种子(以可重复的方式!)。例如。也许有一些简单明了的方法可以从单个整数中生成一个由12个伪随机整数组成的漂亮流(由于数组长度如此之短,这可能很容易吗?)。

编辑添加:有关在fortran中设置种子的后续问题:Correctly setting random seeds for repeatability

最佳答案

在我看来,您提出的解决方案看起来应该可行-您正在记录生成器的整个状态(通过get),并在必要时在流之间进行交换(通过put)。请注意,尽管如此,我尚未实际测试您的代码。

之所以出现这个答案,是因为先前的答案(现在已删除)仅保存了状态数组的第一个元素,并使用它来设置整个状态数组。它看起来像:

integer :: seed_scalar, state_array(12)
...
! -- Generate a random number from a thread that is stored as seed_scalar
state_array(:) = seed_scalar
call random_seed( put=state_array )
call random_number( ran )
call random_seed( get=state_array )
seed_scalar = state_array(1)
! -- discard state_array, store seed_scalar


该答案旨在证明,对于某些编译器(gfortran 4.8和8.1,pgfortran 15.10)而言,这种仅通过标量维护单独线程的方法会导致不良的RNG行为。

考虑下面的代码,以最初测试随机数生成器。生成了许多随机数(在此示例中为100M),并且通过两种方式监视性能。首先,记录随机数增加或减少的时间。其次,生成条宽为0.01的直方图。 (这显然是一个原始的测试用例,但事实证明足以证明这一点。)最后,还生成了所有概率的估计单西格玛标准差。这使我们能够确定变化何时是随机的或具有统计意义的。

program main
   implicit none
   integer, parameter :: wp = selected_real_kind(15,307)
   integer :: N_iterations, state_size, N_histogram
   integer :: count_incr, count_decr, i, loc, fid
   integer, allocatable, dimension(:) :: state1, histogram
   real(wp) :: ran, oldran, p, dp, re, rt

   ! -- Some parameters
   N_iterations = 100000000
   N_histogram = 100

   call random_seed(size = state_size)
   allocate(state1(state_size), histogram(N_histogram))
   write(*,'(a,i3)') '-- State size is: ', state_size

   ! -- Initialize RNG, taken as today's date (also tested with other initial seeds)
   state1(:) = 20180815
   call random_seed(put=state1)

   count_incr = 0
   count_decr = 0
   histogram = 0
   ran = 0.5_wp      ! -- To yield proprer oldran

   ! -- Loop to grab a batch of random numbers
   do i=1,N_iterations
      oldran = ran

      ! -- This preprocessor block modifies the RNG state in a manner
      ! -- consistent with storing only the first value of it
#ifdef ALTER_STATE
      call random_seed(get=state1)
      state1(:) = state1(1)
      call random_seed(put=state1)
#endif

      ! -- Get the random number
      call random_number(ran)

      ! -- Process Histogram
      loc = CEILING(ran*N_histogram)
      histogram(loc) = histogram(loc) + 1

      if (oldran<ran) then
         count_incr = count_incr + 1
      elseif (oldran>ran) then
         count_decr = count_decr + 1
      else
         ! -- This should be statistically impossible
         write(*,*) '** Error, same value?', ran, oldran
         stop 1
      endif
   enddo

   ! -- For this processing, we have:
   !     re    Number of times this event occurred
   !     rt    Total number
   ! -- Probability of event is just re/rt
   ! -- One-sigma standard deviation is sqrt( re*(rt-re)/rt**3 )

   ! -- Write histogram
   ! -- For each bin, compute probability and uncertainty in that probability
   open(newunit=fid, file='histogram.dat', action='write', status='replace')
   write(fid,'(a)') '# i, P(i), dP(i)'

   rt = real(N_iterations,wp)
   do i=1,N_histogram
      re = real(histogram(i),wp)
      p = re/rt
      dp = sqrt(re*(rt-1)/rt**3)

      write(fid,'(i4,2es26.18)') i, p, dp
   enddo
   close(fid)

   ! -- Probability of increase and decrease
   re = real(count_incr,wp)
   p = re/rt
   dp = sqrt(re*(rt-1)/rt**3)
   write(*,'(a,f20.15)') 'Probability of increasing: ', p
   write(*,'(a,f20.15)') '      one-sigma deviation: ', dp

   re = real(count_decr,wp)
   p = re/rt
   dp = sqrt(re*(rt-1)/rt**3)
   write(*,'(a,f20.15)') 'Probability of decreasing: ', p
   write(*,'(a,f20.15)') '      one-sigma deviation: ', dp

   write(*,'(a)') 'complete'

end program main


未经修改

如果没有预处理器指令ALTER_STATE,我们将按预期使用gfortran的内置PRNG,并且结果符合预期:

enet-mach5% gfortran --version
GNU Fortran (SUSE Linux) 4.8.3 20140627 [gcc-4_8-branch revision 212064]
Copyright (C) 2013 Free Software Foundation, Inc.

GNU Fortran comes with NO WARRANTY, to the extent permitted by law.
You may redistribute copies of GNU Fortran
under the terms of the GNU General Public License.
For more information about these matters, see the file named COPYING

enet-mach5% gfortran -cpp -fcheck=all main.f90 && time ./a.out
-- State size is:  12
Probability of increasing:    0.499970710000000
      one-sigma deviation:    0.000070708606619
Probability of decreasing:    0.500029290000000
      one-sigma deviation:    0.000070712748851
complete

real    0m2.414s
user    0m2.408s
sys     0m0.004s


增大/减小的预期概率为0.5,并且两者都具有估计的不确定性(0.49997从0.5小于0.00007)。带有误差线的直方图为

Histogram for Correct Usage

对于每个面元,与预期概率(0.01)的差异很小,并且通常在估计的不确定性之内。因为我们生成了许多数字,所以所有变化都很小(0.1%的量级)。基本上,此测试未发现任何可疑行为。

有修改

如果启用ALTER_STATE中的块,则每次生成数字时,我们都会修改随机数生成器的内部状态。这旨在模拟现在已删除的解决方案,该解决方案仅存储状态的第一个值。结果是:

enet-mach5% gfortran -cpp -DALTER_STATE -fcheck=all main.f90 && time ./a.out
-- State size is:  12
Probability of increasing:    0.501831930000000
      one-sigma deviation:    0.000070840096343
Probability of decreasing:    0.498168070000000
      one-sigma deviation:    0.000070581021884
complete

real    0m16.489s
user    0m16.492s
sys     0m0.000s


观察到的增加概率远远超出预期的变化范围(26 sigma!)。这已经表明出了点问题。直方图为:

Histogram for Incorrect Usage

注意,y的范围已发生实质性变化。在这里,我们的变化比以前的情况大了大约两个数量级,远远超出了预期的变化。这里的误差线很难看到,因为y范围很大。如果我的随机数生成器的表现如此,我将不满意将其用于任何事物,甚至不进行硬币翻转。

闭幕

putgetrandom_seed选项访问随机数生成器的处理器相关内部状态。它通常比单个数具有更多的熵,并且其形式取决于处理器。不能保证第一个数字完全代表整个州。

如果要初始化一次随机种子并生成多次,则使用单个标量就可以了。但是,如果您打算使用该状态来生成每个数字,则显然有必要存储多个数字。

坦率地说,我对此原始测试能够证明不良行为感到有些惊讶。 RNG的有效性是一个复杂的主题,我绝不是专家。结果还取决于编译器:


显示的结果和直方图针对的是gfortran 4.8,其状态大小为12。
英特尔16.0使用的状态大小为2,该测试未显示任何不良行为。
gfortran 8.1.0的状态大小为33,而PGI 15.10的状态大小为34。它们的行为相同,并且性能最差。将整个状态设置为单个值时,后续随机数始终相同。从单个种子初始化时,这些生成器需要大约30代的“启动”才能使数字看起来合理。当仅在状态中保存单个种子时,这种启动不会发生。


仅保存单个值时,较大的状态大小可能会导致更多的熵损失,因此它与较差的行为相关。这当然符合我的观察。但是,如果不了解每个生成器的内部,就无法分辨。

关于fortran - 随机数生成器(RNG/PRNG)返回种子的更新值,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51859479/

相关文章:

c++ - C++ 中的通用 block 等价物

fortran - 指向父类的多态指针不起作用

fortran - 较新的 gfortran 不编译 Fortran90 代码

c++ - GNU Fortran 和 C 互操作性

fortran - 作为派生类型组件的指针

matlab - 在 Fortran 中实现匿名函数

linux - 在 ubuntu 32 位上构建 trilinos 时出现内部 cmake 错误

Fortran 查询返回内部文件

fortran - 我们真的可以在所有情况下都避免 goto 吗?

c - Fortran 的准确性和速度与 C 的对比