随机排列和随机种子

Random permutation and random seed

我正在使用 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(可重复,image_distinct)

问题

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

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

您当然不应该重复调用 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会得到错误的结果?

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

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

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