【问题标题】:Random permutation and random seed随机排列和随机种子
【发布时间】:2021-07-28 15:52:18
【问题描述】:

我正在使用 Knuth 算法来生成随机排列 的一个 n 元组。这是代码。固定 n,它生成随机排列并收集所有不同的排列,直到找到所有 n!排列。最后,它还会打印找到所有排列所需的试验次数。从那时起,我还插入了种子的初始化(不过,以一种非常简单和幼稚的方式)。有两个选项(A 和 B)。 A:种子在主程序中是一次性固定的。 B:每次计算随机排列时,种子都是固定的(第二个选项下方有注释)。

implicit none
 

  integer :: n,ncomb
  integer :: i,h,k,x
  integer, allocatable :: list(:),collect(:,:)
  logical :: found
  integer :: trials
  !
  ! A
  !
  integer :: z,values(1:8)
  integer, dimension(:), allocatable :: seed
  call date_and_time(values=values)
  call random_seed(size=z)
  allocate(seed(1:z))
  seed(:) = values(8)
  call random_seed(put=seed)
 

  n=4
  
  
  ncomb=product((/(i,i=1,n)/))
  allocate(list(n))
  allocate(collect(n,ncomb))  
  
 trials=0 
 h=0
 do 
  trials=trials+1
  list=Shuffle(n)


  found=.false.
  do k=1,h
   x=sum(abs(list-collect(:,k))) 
   if ( x == 0 ) then
     found=.true.
     exit
   end if
  end do

  if ( .not. found ) then
   h=h+1
   collect(:,h)=list
   print*,h,')',collect(:,h)
  end if

  if ( h == ncomb ) exit
 end do 
  
 write(*,*) "Trials= ",trials
  
  
contains
 
function Shuffle(n) result(list)
  integer, allocatable :: list(:)
  integer, intent(in) :: n
  integer :: i, randpos, temp,h
  real :: r
  !
  ! B
  !
!   integer :: z,values(1:8)
!   integer, dimension(:), allocatable :: seed
!   call date_and_time(values=values)
!   call random_seed(size=z)
!   allocate(seed(1:z))
!   seed(:) = values(8)
!   call random_seed(put=seed)

 
 
  allocate(list(n))
  list = (/ (h, h=1,n) /)
  do i = n, 2, -1
    call random_number(r)
    randpos = int(r * i) + 1
    temp = list(randpos)
    list(randpos) = list(i)
    list(i) = temp
  end do
 
end function Shuffle
 
end 

您可以检查第二个选项是否不好。对于 n=4,它需要大约 100 倍的试验才能获得排列的总数,而对于 n=5,它会卡住。

我的问题是:

  1. 为什么多次调用 random_seed 会给出错误的结果?我正在引入什么样的系统错误?是不是相当于只调用一次随机种子,多次启动代码(每次只产生一个随机排列)?

  2. 如果我想启动多次代码,计算一个排列,我猜如果我初始化随机种子我有同样的问题(不管初始化的位置,因为现在我只计算一个排列)。正确的?在这种情况下,我必须做什么才能在不破坏均匀采样的情况下初始化种子?因为如果我不以随机方式初始化种子,我会获得相同的排列。我想我可以在每次启动代码时打印和读取种子,以免从相同的伪随机数开始。但是,如果我并行启动多个代码实例,这会很复杂。

更新

我已经明白回复了。总之,如果我想通过初始化种子在每次调用时生成伪随机数,我可以做的是:

A) 旧的 gfortran

在这里使用子程序 init_random_seed()

https://gcc.gnu.org/onlinedocs/gcc-5.1.0/gfortran/RANDOM_005fSEED.html

B) 最新的 gfortran 版本

调用 random_seed()

C) Fortran2018

调用 random_init(repeatable, image_distinct)

问题

在 C) 的情况下,我应该设置 repeatable=.false., image_distinct=.true. 每次都有不同的随机数?

以可移植方式编写代码的有效方法是什么? 无论编译器是什么,它都可以工作吗? (我的意思是,代码可以识别可用的内容并相应地工作)

【问题讨论】:

  • 究竟是什么“为什么会这样?”?你到底关心什么?
  • 现在清楚了吗?
  • 只有在使用 Fortran 的 coarray 功能时,random_initimage_distinct 参数才重要。

标签: fortran permutation random-seed


【解决方案1】:

您当然不应该反复拨打random_seed()。它应该只被调用一次。如此粗暴地设置问题,会让问题变得更糟。

是的,确实经常使用数据和时间来初始化它,但是必须通过一些增加熵的东西来预处理时间数据,比如通过一些非常简单的随机发生器。对于旧版本的 gfortran,可以在 RANDOM_SEED 的文档中找到一个很好的示例:https://gcc.gnu.org/onlinedocs/gcc-5.1.0/gfortran/RANDOM_005fSEED.html 了解如何使用 lcg() 来转换 data_and_time() 数据。

请注意,最新版本的 gfortran 将生成一个每次都不同的随机种子,只需调用 random_seed() 而无需任何参数。旧版本每次都返回相同的种子。

另请注意,Fortran 2018 具有 random_init(),您可以在其中将 repeatable= 指定为 true 或 false。使用 false 时,您每次都会得到不同的序列。

可移植的就是使用标准的 Fortran,仅此而已。但是您不能同时使用新功能和旧编译器版本。这种便携性是不存在的。使用旧的编译器,您只能使用旧的标准功能。我什至不会开始写 autoconf 之类的东西,这不值得。


所以,

  1. 您可以将随机数种子设置为每次都相同或每次都不同(见上文),

  1. 您应该始终拨打random_seedrandom_init只拨打一次

为什么多次调用 random_seed 会给出错误的结果?

您正在将伪随机序列重新启动到某个未指定的状态,可能熵不足。很容易接近最后的起始状态。

我引入了什么样的系统错误?是不是相当于只调用一次随机种子,多次启动代码(每次只产生一个随机排列)?

可能类似。但是您使用时间播种太天真了,当在循环中运行时,如果在大多数位中不完全相等,日期和时间就太相似了。上面链接的一些转换可能会掩盖这个问题,但是将日期和时间本身作为种子是行不通的。

【讨论】:

  • 感谢您的回复。实际上是的,即使我在 Shuffle 子例程中使用您链接的子例程 init_random_seed(),它也不能正常工作。似乎如此快速的重复调用并不能有效地改变种子。不过,我仍然想念你的回复。请让我的问题更准确一些。
  • @Gippo 我不确定您可能还缺少什么。只有一条主要消息:RUN RANDOM_SEED 或 RANDOM_INIT ONLY ONCE。不管你想做多少计算,只运行一次。无论您进行多少次独立计算,都无需重新启动伪随机序列。继续使用你拥有的单个伪随机序列。
  • 我已经编辑了这个问题。感谢您的帮助
  • 好的。 image_distinct 的值不相关?
  • @Gippo 除非您将多个图像与 coarrays 一起使用,否则不会。
猜你喜欢
  • 1970-01-01
  • 2016-08-12
  • 2016-10-07
  • 1970-01-01
  • 1970-01-01
  • 2014-11-21
  • 2012-06-24
相关资源
最近更新 更多