如何在 Fortran mex 文件中实现 A = sparse(I, J, K)来自三元组的稀疏矩阵?

问题描述

我正在尝试通过 mex 函数(用 Fortran 编写)在 Matlab 中创建一个稀疏方阵。我想要类似 A = sparse(I,J,K) 的东西。我的三胞胎是这样的,条目之间有重复

femi = [1,2,3,4,5,6,2]
femj = [2,1,4]
femk = [2,7,4]

我写了一段粗略的代码,它适用于小矩阵维度,但比固有的 Matlab 的 sparse 慢得多。因为我几乎没有编码背景,所以我不知道我做错了什么(分配变量的方式错误?循环太多了?)。任何帮助表示赞赏。谢谢你。这是 mex 计算子程序。它返回 pr,ir,jc 索引数组以提供给稀疏矩阵

subroutine new_sparse(femi,femj,femk,pr,jc,n,m)

      implicit none
      intrinsic:: SUM,COUNT,ANY
      integer :: i,j,k,indjc,m
      real*8  :: femi(n),femj(n),femk(n)
      real*8  :: pr(n)
      integer :: ir(n),jc(m+1)
      logical :: indices(n)

      indices = .false.
      k = 1
      indjc = 0
      jc(1) = 0
      do j=1,m
            do i =1,m
                indices = [femi==i .and. femj==j]
                if (ANY(indices .eqv. .true.)) then                                     
                    ir(k) = i-1
                    pr(k) = SUM(femk,indices)
                    k = k+1
                    indjc = indjc + 1
                end if
            end do
            if (indjc/=0) then
                jc(j+1) = jc(j) + indjc
                indjc = 0
            else
                jc(j+1) = jc(j) 
            end if
      end do


      return      
      end

编辑:
正如用户@jack 和@veryreverie 在下面的评论中所建议的那样,最好直接对femifemjfemk 进行排序。我猜首先对 femi 进行排序/排序(并根据 femjfemkfemi 进行排序),然后对 femj 进行排序/排序(并排序 {{1 }} 和 femi 根据 femk) 提供所需的结果。剩下的唯一事情就是处理重复项。

编辑 #2:
我用 Engblom and Lukarksi 逐行翻译了 C 代码的序列化版本。该文档非常清楚地解释了他们的推理,我认为它对像我这样的初学者很有用。但是,由于我的经验不足,我无法翻译代码的并行版本。也许这会引发另一个问题。

femj

解决方法

这应该有效:

module test
  implicit none
  
  ! This should probably be whatever floating point format Matlab uses.
  integer,parameter :: dp = selected_real_kind(15,300)
contains
subroutine new_sparse(femi,femj,femk,pr,ir,jc,n,m)
  integer,intent(in) :: n ! The size of femi,femk.
  integer,intent(in) :: m ! The no. of rows (and cols) in the matrix.
  integer,intent(in) :: femi(n) ! The input i indices.
  integer,intent(in) :: femj(n) ! The input j indices.
  real(dp),intent(in) :: femk(n) ! The input values.
  real(dp),intent(out) :: pr(n) ! The output values.
  integer,intent(out) :: ir(n) ! The output i indices.
  integer,intent(out) :: jc(m+1) ! Column j has jc(j+1)-jc(j) non-zero entries
  
  ! loop indices.
  integer :: a,b
  
  ! Initialise jc.
  ! All elements of `jc` are `1` as the output initially contains no elements.
  jc = 1
  
  ! Loop over the input elements.
  do_a : do a=1,n
    associate(i=>femi(a),j=>femj(a),k=>femk(a))
      ! Loop over the stored entries in column j of the output,!    looking for element (i,j).
      do b=jc(j),jc(j+1)-1
        ! Element (i,j) is already in the output,update the output and cycle.
        if (ir(b)==i) then
          pr(b) = pr(b) + femk(a)
          cycle do_a
        endif
      enddo
      
      ! Element (i,j) is not already in the output.
      ! First make room for the new element in ir and pr,!    then add the element to ir and pr,!    then update jc.
      ir(jc(j+1)+1:jc(m+1)) = ir(jc(j+1):jc(m+1)-1)
      pr(jc(j+1)+1:jc(m+1)) = pr(jc(j+1):jc(m+1)-1)
      ir(jc(j+1)) = i
      pr(jc(j+1)) = k
      jc(j+1:) = jc(j+1:) + 1
    end associate
  enddo do_a
end subroutine
end module

program prog
  use test
  implicit none
  
  integer,parameter :: n = 14
  integer,parameter :: m = 6
  
  integer  :: femi(n),femj(n)
  real(dp) :: femk(n)
  
  real(dp) :: pr(n)
  integer  :: ir(n),jc(m+1)
  
  integer :: a,b
  
  femi = [1,2,3,4,5,6,2]
  femj = [2,1,4]
  femk = real([2,7,4],dp)
  
  write(*,*) 'Input:'
  do a=1,n
    write(*,'(a,i0,a,f2.0)') '(',femi(a),',femj(a),') : ',femk(a)
  enddo
  
  write(*,*)
  
  call new_sparse(femi,m)
  
  write(*,*) 'Output:'
  do a=1,m
    do b=jc(a),jc(a+1)-1
      write(*,ir(b),pr(b)
    enddo
  enddo
end program

这样写道:

Input:
(1,2) : 2.
(2,2) : 1.
(3,1) : 5.
(2,1) : 4.
(2,1) : 2.
(4,3) : 4.
(5,3) : 5.
(5,6) : 7.
(4,3) : 2.
(6,1) : 1.
(6,1) : 6.
(5,2) : 2.
(5,2) : 1.
(2,4) : 4.

Output:
(3,1) : 6.
(6,1) : 7.
(1,2) : 1.
(5,2) : 3.
(4,3) : 6.
(5,3) : 5.
(2,4) : 4.
(5,6) : 7.

算法的瓶颈来自于指令 indices = [femi==i .and. femj==j]any(indices .eqv. .true.)sum(femk,indices)。这些都需要 O(n) 次操作,并且由于这些在双循环中,子程序的总成本为 O(m^2*n)

我的算法分两个阶段工作。第一阶段,do b=jc(j),jc(j+1)-1 循环,将输入中的每个元素与输出的匹配列中的每个元素进行比较,以获得 O(mn) 操作的最大成本。如果在输出中找到输入元素,则更新该值,无需执行任何操作。

如果在输出中找不到输入元素,则需要将其添加到输出中。这由第二阶段处理,即 do b... 循环之后的代码。由于这需要移动输出元素以便为新元素腾出空间,因此该阶段最多有 O(n'^2) 次操作,其中 n' 是输入中唯一元素的数量,它应该满足n'<=nn'<<m^2 用于稀疏矩阵。

对于大 mn,我的算法应该运行得更快,但它肯定有很大的改进空间。我怀疑使用中间数据结构来存储 irpr 是值得的,这样就可以插入新元素而无需重新排列所有元素。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...