【问题标题】:generating efficient sub block matrices to avoid duplicated code生成有效的子块矩阵以避免重复代码
【发布时间】:2020-02-29 20:34:08
【问题描述】:

以下代码可以正常工作。我生成了一个 9x9 矩阵,它由 9 个单独的 3x3 子块矩阵组成。但是,很多代码似乎是重复的,而且我似乎使用了过多的 DO 循环。

有什么方法可以生成相同的 9x9 矩阵,但无需复制大量代码并最大限度地减少 do 循环的数量?

我的实际问题涉及一个比 9x9 矩阵大得多的方阵,所以这个 TestCode 不是那么通用或有用,尽管它可以工作。

此处矩阵为 9x9 的情况只是一个最小的、完整的、可验证的示例。一般来说,我需要对 n x n 矩阵执行此操作,其中每个子块的大小为 sqrt(n) x sqrt(n)。

PROGRAM TestCode

IMPLICIT NONE
INTEGER :: i, j, m!matrix indices (i,j)
INTEGER,PARAMETER :: n = 9 ! matrix is 9x9
DOUBLE PRECISION :: KE(n,n)
REAL :: nn 

nn = n
m = SQRT(nn)

DO i = 1, m
    DO j = 1, m
        IF( i .EQ. j) THEN 
            KE(i,j) = -4
        ELSEIF ( ABS(i-j) .EQ. 1) THEN
            KE(i,j) = 1
        ELSE 
            KE(i,j) = 0
        END IF
    END DO
END DO

DO i = 4,6
    DO j = 4,6
        IF( i .EQ. j) THEN 
            KE(i,j) = -4
        ELSEIF ( ABS(i-j) .EQ. 1) THEN
            KE(i,j) = 1
        ELSE 
            KE(i,j) = 0
        END IF
    END DO
END DO

DO i = 7,9
    DO j = 7,9
        IF( i .EQ. j) THEN 
            KE(i,j) = -4
        ELSEIF ( ABS(i-j) .EQ. 1) THEN
            KE(i,j) = 1
        ELSE 
            KE(i,j) = 0
        END IF
    END DO
END DO

DO i = 4,6
    DO j = 1,m
        IF( ABS(i-j) .EQ. m) THEN 
            KE(i,j) = 1
        ELSE 
            KE(i,j) = 0
        END IF
    END DO
END DO

DO i = 7,9
    DO j = 1,m
        KE(i,j) = 0
    END DO
END DO

DO i = 1,m
    DO j = 4,6
        IF ( ABS(i-j) .EQ. m) THEN
            KE(i,j) = 1
        ELSE
            KE(i,j) = 0
        END IF
    END DO
END DO

DO i = 7,9
    DO j = 4,6
        IF ( ABS(i-j) .EQ. m) THEN
            KE(i,j) = 1
        ELSE
            KE(i,j) = 0
        END IF
    END DO
END DO

DO i = 1,m
    DO j = 7,9
        KE(i,j) = 0
    END DO
END DO

DO i = 4,6
    DO j = 7,9
        IF( ABS(i-j) .EQ. m) THEN
            KE(i,j) = 1
        ELSE
            KE(i,j) = 0
        END IF
    END DO
END DO

END PROGRAM

【问题讨论】:

  • this other question 有帮助吗?
  • @francescalus 感谢您提出这个问题。我在发这个帖子之前就找到了那个帖子。我不完全确定你在使用数组构造函数的答案中做了什么,所以我不想只是复制代码,因为我根本不理解它。我理解我上面写的代码,所以我试图根据我自己理解的东西来简化它。如果您认为我的问题是重复的,我确实理解。但是,我使用 VladimirF 的答案而不是您的数组构造方法答案会更容易吗?
  • @francescalus 另外,这里的矩阵为 9x9 的情况只是一个最小的、完整的、可验证的示例。一般来说,我需要对 n x n 矩阵执行此操作,其中每个子块的大小为 sqrt(n) x sqrt(n)。所以我认为我的问题可能与您建议的帖子略有不同。虽然我可能只是错过了联系。谢谢
  • 糟糕,我没有认出我的答案,所以我必须重新阅读问题/答案才能回答您的观点。 (不是重复的建议,因为我没有充分的时间去复习。)
  • 你可以做的一件事就是切换索引,Fortran 是主要列。

标签: arrays matrix fortran


【解决方案1】:

鉴于数据的周期性,子矩阵和整个块矩阵可以通过PAD= 参数填充到RESHAPE 内在函数。

program blox
   implicit none
   integer m
   integer n
   integer, allocatable :: zero(:), sp3(:,:,:)
   integer, allocatable :: b1(:,:), b2(:,:), b3(:,:)
   integer, allocatable :: iKE(:,:)
   character(80) fmt
   m = 4
   n = m**2
   zero = reshape([integer::],[m+1],pad=[0])
   b1 = reshape([integer::],[m,m],pad=[-4,1,zero(1:m-2),1])
   b2 = reshape([integer::],[m,m],pad=[1,zero(1:m)])
   b3 = reshape([integer::],[m,m],pad=zero(1:m+1))
   sp3 = reshape([integer::],[m,m,m-2],pad=b3)
   iKE = reshape(reshape([integer::],[m,m,m,m],pad=[b1,b2,sp3,b2],order=[1,3,2,4]),[n,n])
   write(fmt,'(*(g0))') '(',m,'(',m,'(',m,'i3:1x)/))'
   write(*,fmt) transpose(iKE)
end program blox

注意如何通过用零填充空数组 (zero) 创建零字符串,然后通过用单个数据周期填充空数组数组 (bl, @) 填充具有周期性数据的数组987654327@ 和 b3)。然后,通过用块 (sp3) 填充空数组来创建由块矩阵的副本组成的矩阵。最后,通过用块序列填充一个空数组来创建一个块周期矩阵。必须以交叉顺序读取生成的矩阵,然后将其重新整形为正确的维度 (iKE)。

输出:

 -4  1  0  0   1  0  0  0   0  0  0  0   0  0  0  0
  1 -4  1  0   0  1  0  0   0  0  0  0   0  0  0  0
  0  1 -4  1   0  0  1  0   0  0  0  0   0  0  0  0
  0  0  1 -4   0  0  0  1   0  0  0  0   0  0  0  0

  1  0  0  0  -4  1  0  0   1  0  0  0   0  0  0  0
  0  1  0  0   1 -4  1  0   0  1  0  0   0  0  0  0
  0  0  1  0   0  1 -4  1   0  0  1  0   0  0  0  0
  0  0  0  1   0  0  1 -4   0  0  0  1   0  0  0  0

  0  0  0  0   1  0  0  0  -4  1  0  0   1  0  0  0
  0  0  0  0   0  1  0  0   1 -4  1  0   0  1  0  0
  0  0  0  0   0  0  1  0   0  1 -4  1   0  0  1  0
  0  0  0  0   0  0  0  1   0  0  1 -4   0  0  0  1

  0  0  0  0   0  0  0  0   1  0  0  0  -4  1  0  0
  0  0  0  0   0  0  0  0   0  1  0  0   1 -4  1  0
  0  0  0  0   0  0  0  0   0  0  1  0   0  1 -4  1
  0  0  0  0   0  0  0  0   0  0  0  1   0  0  1 -4

令人惊讶的是,您确实需要此矩阵的显式形式。通常你会看到通过稀疏矩阵技术更间接地处理这种对象。

【讨论】:

  • 感谢详细的解决方案。它编译并运行产生我正在寻找的确切结果。我需要考虑一下你在这里做什么,因为这段代码相对于我以前做的有点先进。为什么打印出 iKE 的 Transpose 而不仅仅是 iKE? iKE 和 transpose(iKE) 不相等吗?我认为我需要这个矩阵,因为我正在尝试生成 2D 拉普拉斯有限差分离散化矩阵。也许使用稀疏矩阵技术效率更高,但我不认为这样做......
  • 我打印了transpose(iKE),因为 Fortran 以列优先顺序存储矩阵,但除非您习惯于从转置中读取矩阵,否则它们应该以行优先顺序打印出来。当然,对称矩阵无关紧要,但在我看来,看到矩阵打印出主要行并不那么令人惊讶。如果您采用某种稀疏矩阵技术,您可能能够解决更大的问题。 First link I tried.
  • 谢谢,您的解释很有道理。我还有一个问题,制作sp3有什么意义?它不只是一个零数组吗?我对b3和sp3之间的区别感到困惑。当你制作 b1,b2,b3 时,我可以看到你在做什么。另外,您为什么将它们全部设为整数,可分配而不是 REAL?您是否需要使它们可分配 - 我从不使用可分配数组,我通常在程序开始时定义大小。再次感谢您的帮助
  • 有几种不同的方法可以将一个数组的副本打包到另一个数组中,而不是把书扔给你,我只是使用了其中一种。 sp3m-2b3 的副本,我编写了这个示例,即使.NOT.all(b3==0) 也能正常工作。它们在语法上是不同的:尝试sp3=b3b3=sp3,即使m==3 包含相同的元素序列。我本可以使用spread(0,1,m**2*(m-2)) 代替sp3 从而节省2 行代码,实际上我可以用一个可执行语句替换所有这些reshapes,但它会很难阅读。跨度>
  • 为了制作副本,RESHAPEPAD= 参数表示“如果您没有足够的数据,请追加副本直到获得足够的数据”。 SPREAD 说'制作NCOPIES= 数组的SOURCE= 副本并制作新维度DIM=EOSHIFT 可以像 Sagittarius A* 一样吸收 SHIFT= 数组的 BOUNDARY= 副本。我想如果我使用它,我可以避免交叉顺序索引。一切都是可分配的,因此如果需要,网格尺寸可以改变。我需要整数输出,以便它适合一个屏幕。不过,我本可以在最后使用 double precision KE 而不是 integer iKE
【解决方案2】:

在 Fortran 中有很多制作块矩阵的方法,@francescalus 已经指出的答案显示了一些,我的工具箱中肯定有一些。这是另一种方法,可能更简单但满足 OP 的直接需求。

首先,为块声明一个变量,在下面它被称为blk,它被声明为m*m。那么就这么简单...

 ke = 0.0  ! All elements will be 0.0 unless otherwise assigned

 ! Define the block on the diagonal, and assign it
 blk = RESHAPE([-4.0, 1.0, 0.0, 1.0, -4.0, 1.0, 0.0, 1.0, -4.0], [m,m])
 DO i = 1, n, m
    ke(i:i+m-1,i:i+m-1) = blk
 END DO

 ! Now the off-diagonal blocks
 blk = RESHAPE([1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0], [m,m])
 DO i = 4, n, m
    ke(i:i+m-1,i-m:i-1) = blk
    ke(i-m:i-1,i:i+m-1) = blk
 END DO

如果我打算在生产代码中使用它,那么我会将它包装到一个例程中,并更多地考虑诸如使用可分配数组、传递假定形状的数组等问题。

Fortran 程序员库中适用的其他有用工具是chsifteoshift。我可能会使用后者如下。首先,我们有一个 n-element 的双精度数组,称为 line,然后

 ke = 0.0

 line = [0.0, 1.0, 0.0, 1.0, -4.0, 1.0, 0.0, 1.0, 0.0]

 ke((n/2)+1,:) = line
 DO i = 1, 4
    ke(i,:) = EOSHIFT(line,m+2-i)
    ke(n-i+1,:) = EOSHIFT(line,-(m+2-i))
 END DO

关于效率问题,我怀疑这里的任何方法或类似方法之间是否存在很多差异。我认为最小化数组的扫描次数是个好主意,并且以内存友好的顺序访问元素也很有用。但他们都必须至少访问每个元素一次。我会选择最直接的方法,即 发现对于手头的问题最直接的方法;您对问题的选择可能会有所不同。仅在分析显示存在问题时才担心效率。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-02-21
    • 1970-01-01
    • 2014-03-07
    • 1970-01-01
    • 2020-01-25
    • 2023-03-24
    相关资源
    最近更新 更多