【问题标题】:Fortran: passing arbitrary "structures" to a module subroutineFortran:将任意“结构”传递给模块子例程
【发布时间】:2014-11-22 22:04:07
【问题描述】:

我正在尝试编写一个用于最小化的通用子例程。因为我想要一个通用的子程序,所以目标函数可以有不同的参数,不仅在名称上,而且在维度上。所以我需要一种方法来传递参数结构(我使用结构这个词,因为我的想法是在 Matlab 中使用类似结构类型变量的东西)。 我设法使用了派生数据类型,它工作得很好,但是当我在同一个程序中有两个不同的目标函数时,问题就出现了。这是一个示例代码:

在主程序main.f90中:

MODULE MYPAR
  IMPLICIT NONE
  TYPE PARPASS1    !! Parameter passing structure 1
     INTEGER       :: a
     REAL          :: X(2,2)
  END TYPE PARPASS1

  TYPE PARPASS2    !! Parameter passing structure 2
     REAL          :: b
     REAL          :: Y(3)
  END TYPE PARPASS2
END MODULE MYPAR

PROGRAM MAIN

  USE MYPAR
  USE MYLIB
  IMPLICIT NONE

  INTEGER        :: am
  REAL           :: bm,Xm(2,2),Ym(3),sol1,sol2
  TYPE(PARPASS1) :: PARAM1
  TYPE(PARPASS2) :: PARAM2

  am = 1
  bm = 3.5
  Xm(1,:) = [1.0, 2.0]
  Xm(2,:) = [0.5, 2.5]
  Ym(1:3) = [0.25,0.50,0.75]

  PARAM1%a = am
  PARAM1%X = Xm
  PARAM2%b = bm
  PARAM2%Y = Ym

  CALL MYSUB(sol1,OBJ1,PARAM1)
  CALL MYSUB(sol2,OBJ2,PARAM2)
  PRINT *,sol1
  PRINT *,sol2

CONTAINS

  SUBROUTINE OBJ1(sumval,PARAM)
    REAL,INTENT(OUT)          :: sumval
    TYPE(PARPASS1),INTENT(IN) :: PARAM
    INTEGER                   :: a
    REAL,ALLOCATABLE          :: X(:,:)
    a = PARAM%a
    X = PARAM%X
    sumval = a+X(1,1)+X(2,2)
  END SUBROUTINE OBJ1

  SUBROUTINE OBJ2(divval,PARAM)
    REAL,INTENT(OUT)          :: divval
    TYPE(PARPASS2),INTENT(IN) :: PARAM
    REAL                      :: b
    REAL,ALLOCATABLE          :: Y(:)
    b = PARAM%b
    Y = PARAM%Y
    divval = b / (Y(1)+Y(2))
  END SUBROUTINE OBJ2

END PROGRAM MAIN

还有一个名为 mylib.90 的模块

MODULE MYLIB

  USE MYPAR
  IMPLICIT NONE

CONTAINS

  SUBROUTINE MYSUB(sol,FN,PARAM)
    REAL,INTENT(OUT)           :: sol
    TYPE(PARPASS1), INTENT(IN) :: PARAM
    CALL FN(sol,PARAM)
    sol = 2*sol
  END SUBROUTINE MYSUB

END MODULE MYLIB

显然,如果我用CALL MYSUB(sol2,OBJ2,PARAM2)PRINT *,sol2 注释这些行,我的代码运行顺利。这是我在拥有两个“目标函数”之前拥有的,但现在当我拥有它们时它不起作用,因为 MYSUB 中的派生类型变量 PARPASS1 不能是任意的。

有什么想法吗?

【问题讨论】:

    标签: fortran gfortran


    【解决方案1】:

    您可以使用接口并重载子例程 MYSUB:

    MODULE MYLIB
    
      USE MYPAR
      IMPLICIT NONE
    
      interface MYSUB
        module procedure MYSUB_PARPASS1, MYSUB_PARPASS2
      end interface
    
    CONTAINS
    
      SUBROUTINE MYSUB_PARPASS1(sol,FN,PARAM)
        REAL,INTENT(OUT)           :: sol
        TYPE(PARPASS1), INTENT(IN) :: PARAM
        CALL FN(sol,PARAM)
        sol = 2*sol
      END SUBROUTINE MYSUB_PARPASS1
    
      SUBROUTINE MYSUB_PARPASS2(sol,FN,PARAM)
        REAL,INTENT(OUT)           :: sol
        TYPE(PARPASS2), INTENT(IN) :: PARAM
        CALL FN(sol,PARAM)
        sol = 2*sol
      END SUBROUTINE MYSUB_PARPASS2   
    
    END MODULE MYLIB
    

    然后你可以使用MYSUB调用它,它会根据PARAM的TYPE来区分函数

    编辑:好的,这个怎么样:

    MODULE MYPAR
      IMPLICIT NONE
    
      type, abstract :: PARPASS
       contains
         procedure(func), deferred :: OBJ
      end type PARPASS
    
      TYPE, extends(PARPASS) :: PARPASS1    !! Parameter passing structure 1
         INTEGER       :: a
         REAL          :: X(2,2)
       contains
         procedure :: OBJ => OBJ1
      END TYPE PARPASS1
    
      TYPE, extends(PARPASS) :: PARPASS2    !! Parameter passing structure 2
         REAL          :: b
         REAL          :: Y(3)
       contains
         procedure :: OBJ => OBJ2
      END TYPE PARPASS2
    
      abstract interface
         subroutine func(this, val) !Interface for the subroutine you want to implement
           import
           class(PARPASS), intent(in) :: this
           real, intent(out) :: val
         end subroutine func
      end interface
    
    contains
    
       subroutine OBJ1(this, val)
        class(PARPASS1),INTENT(IN) :: this
        real, intent(out)          :: val
        INTEGER                    :: a
        REAL,ALLOCATABLE           :: X(:,:)
        a = this%a
        X = this%X
        val = a+X(1,1)+X(2,2)
      END subroutine OBJ1
    
      subroutine OBJ2(this, val)
        class(PARPASS2),INTENT(IN) :: this
        real, intent(out)          :: val
        REAL                       :: b
        REAL,ALLOCATABLE           :: Y(:)
        b = this%b
        Y = this%Y
        val = b / (Y(1)+Y(2))
    
      END subroutine OBJ2
    
    END MODULE MYPAR
    
    
    MODULE MYLIB
    
      USE MYPAR
      IMPLICIT NONE
    
    CONTAINS
    
      SUBROUTINE MYSUB(sol, param)
        REAL,INTENT(OUT)           :: sol
        class(PARPASS), INTENT(IN) :: PARAM
        call param%obj(sol)
        sol = 2*sol
      END SUBROUTINE MYSUB
    
    END MODULE MYLIB
    
    PROGRAM MAIN
    
      USE MYPAR
      USE MYLIB
      IMPLICIT NONE
    
      INTEGER        :: am
      REAL           :: bm,Xm(2,2),Ym(3),sol1,sol2
      TYPE(PARPASS1) :: PARAM1
      TYPE(PARPASS2) :: PARAM2
    
      am = 1
      bm = 3.5
      Xm(1,:) = [1.0, 2.0]
      Xm(2,:) = [0.5, 2.5]
      Ym(1:3) = [0.25,0.50,0.75]
    
      PARAM1%a = am
      PARAM1%X = Xm
      PARAM2%b = bm
      PARAM2%Y = Ym
    
      CALL MYSUB(sol1, PARAM1)
      CALL MYSUB(sol2, PARAM2)
      PRINT *,sol1
      PRINT *,sol2
    
    END PROGRAM MAIN
    

    它使用包含过程OBJ 的抽象类型,然后您的派生类型可以扩展它并实现实际过程。然后,您可以将扩展 PARPASS 并实现类型绑定过程 OBJ 的任何类型传递给“MYSUB”,并从内部调用它,而无需为所有不同的可能性提供单独的接口。

    【讨论】:

    • 谢谢!这将解决当前的问题,但不幸的是无论如何都不是通用的解决方案。如果有人需要拨打 MYSUB 3 次或更多次怎么办?为不同的 PARPASS 类型复制和粘贴 MYSUB 的 n 个副本几乎没有意义,因为拥有通用子例程的想法(在我的理解中)不必修改它。难道没有其他方法可以解决这个问题吗?它不应该使用派生数据类型,这只是我最初的方法。
    • @Lord_77 我添加了一个应该更具扩展性的新解决方案
    • 只是一个附带问题。是否可以将类型名称作为子例程的输入传递?所以我们称之为MYSUB,我只需要输入CALL MYSUB(sol1,'PARPASS1',PARAM1)。在这种情况下,'PARPASS1' 可以是一个字符串,用于在 MYSUB 中声明 PARAM1 的类型。 @kyle-g
    • @Lord_77 你不能在运行时声明这样的类型。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-09
    相关资源
    最近更新 更多