我正在尝试编写一个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
请注意,此解决方案(和任何其他解决方案)的重要方面是如果我们在上面添加seed3
和x3
,则实现的值不会改变x1
和x2
。同样,可以从代码中删除x1
或x2
,而不会影响其他代码的值。
如果有帮助,这是ran()
(扩展名)函数在Compaq / HP和Intel编译器上的工作方式,我基本上是在尝试在gfortran上实现相同的API。但是请注意,该函数的种子是标量,而它是使用Fortran 90子例程random_seed
的一维数组(数组的大小/长度特定于实现)。
我正在提供当前的解决方案作为基准错误,但希望其他人可以批评该回答或提供更好的解决方案。
我希望根据标准PRNG测试对基准结果进行任何分析,尤其是对种子结实的分析。在基准测试中,我仅使用标量广播到一个非常简单明了的数组,并希望避免显式提供整个整数数组。
因此,我想或者稍微严格地确认这很好,还是要比写出整个数组(例如12或33个整数)更简洁的方法来设置种子(以可重复的方式!)。例如。也许有一些简单明了的方法可以从单个整数中生成一个由12个伪随机整数组成的漂亮流(由于数组长度如此之短,这可能很容易吗?)。
编辑以添加:有关在fortran中设置种子的后续问题:Correctly setting random seeds for repeatability
答案 0 :(得分:2)
您提出的解决方案在我看来应该可以使用-您正在记录生成器的整个状态(通过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)。带有误差线的直方图是
对于每个面元,与预期概率(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范围很大。如果我的随机数生成器的性能是这样的,那么我将它用在任何事情上都不会感到舒服,甚至连硬币投掷也不会。
put
的{{1}}和get
选项访问随机数生成器的处理器相关内部状态。它通常比单个数具有更多的熵,并且其形式取决于处理器。不能保证第一个数字完全代表整个州。
如果您要初始化一次随机种子并生成多次,则使用单个标量就可以了。但是,如果您打算使用该状态来生成每个单个数字,显然必须存储多个单个数字。
坦率地说,我对此原始测试能够证明不良行为感到有些惊讶。 RNG的有效性是一个复杂的主题,我绝不是专家。结果还取决于编译器:
仅保存单个值时,较大的状态大小可能会导致更多的熵损失,因此它与较差的行为相关。这当然符合我的观察。但是,如果不了解每个生成器的内部,就无法分辨。