在我看来,您提出的解决方案应该可行 - 您正在记录生成器的整个状态(通过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 - 并以两种方式监控性能。首先,记录随机数增加或减少的次数。其次,生成一个 bin 宽度为 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)。用误差线绘制的直方图是
对于每个 bin,与预期概率 (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!)。这已经表明出了点问题。直方图为:
请注意,y 的范围发生了很大变化。在这里,我们的变化比前一种情况大约大两个数量级,远远超出了预期的变化。在这里很难看到误差线,因为 y 范围要大得多。如果我的随机数生成器是这样运行的,我会觉得用它做任何事情都不会舒服,甚至抛硬币也不行。
结束
random_seed 的put 和get 选项访问随机数生成器的处理器相关内部状态。这通常比单个数字具有更多的熵,并且其形式取决于处理器。不能保证第一个数字完全代表整个州。
如果您要初始化一个随机种子并生成多次,那么使用单个标量就可以了。但是,如果您打算使用该状态来生成每个数字,显然需要存储多个数字。
坦率地说,我有点惊讶这个原始测试能够证明不良行为。 RNG的有效性是一个复杂的主题,我绝不是专家。结果也取决于编译器:
- 显示的结果和直方图适用于 gfortran 4.8,其状态大小为 12。
- 英特尔 16.0 使用状态大小为 2,此测试未显示任何不良行为。
- gfortran 8.1.0 的状态大小为 33,而 PGI 15.10 的状态大小为 34。它们的行为相同,而且是最差的。将整个状态设置为单个值时,后续随机数总是相同。当从单个种子初始化时,这些生成器需要大约 30 代的“启动”才能使数字看起来合理。当在状态中只保存一个种子时,这种启动永远不会发生。
当仅保存单个值时,较大的状态大小可能会导致更多的熵损失,因此它与较差的行为相关。这当然符合我的观察。但是,如果不知道每个生成器的内部结构,就无法分辨。