设计使用Fortran

时间:2018-02-22 17:21:46

标签: oop fortran

整个下午,我一直在撞墙,试图弄清楚这个问题,所以我希望有人可以帮助我。

我有一个名为base_model的抽象基类(比如说),它在Fortran2003中看起来像:

type, abstract :: base_model
contains
  procedure(initMe), pass(this), deferred :: init ! constructor
  procedure(delMe), pass(this), deferred :: delete ! destructor
  procedure(solveMe), pass(this), deferred :: solve
end type base_model

显然,抽象过程initMedelMesolveMe是使用抽象接口块定义的。然后我有三个派生类,称为model1model2model3(比如说​​):

type, extends(base_model) :: model1
  double precision :: x,y
contains
  procedure :: init => init_model1
  procedure :: delete => delete_model1
  procedure :: solve => solve_model1
end type model1

type, extends(base_model) :: model2
contains
  procedure :: init => init_model2
  procedure :: delete => delete_model2
  procedure :: solve => solve_model2
end type model2

type, extends(base_model) :: model3
contains
  procedure :: init => init_model3
  procedure :: delete => delete_model3
  procedure :: solve => solve_model3
end type model3

然后我有一个名为control的“控制”对象(比如说),它扩展了一个抽象的base_control

type, abstract :: base_control
  class(base_model), allocatable :: m1
  class(base_model), allocatable :: m2
  class(base_model), allocatable :: m3
contains
  procedure(initMe), pass(self), deferred :: init
  procedure(delMe), pass(self), deferred :: delete
  procedure(runMe), pass(self), deferred :: run
end type base_control

type, extends(base_control) :: control
contains
  procedure :: init => init_control
  procedure :: delete => delete_control
  procedure :: run => run_control
end type control

可以将对象m1m2m3分配到任何模型中:model1model2model3,以及根据用户请求的“控制”,以任何特定顺序“解决”。

三个可分配对象(m1m2m3)需要在它们之间传递数据。鉴于它们是“控制”对象的成员,我可以为每个模型定义一个“getter”,然后将所需的数据传递到每个模型中。但是,特定模型在编译时是未知的,因此,“控制”对象不知道要获取什么数据,实际上,模型不知道要接收哪些数据!

例如,如果我allocate(model1::m1)(即,将m1分配为类型model1),则它将包含两位数据double precision :: x,y。然后,如果m2被分配为类型model2allocate(model2::m2)),则可能需要x但是如果它被分配为类型model3({ {1}})然后allocate(model3::m2)可能需要y。因此,鉴于“控制”对象无法知道分配的m1类型,如何从m2获取必要的数据以传递到m1

另一个复杂因素是模型之间的相互作用通常是循环的。也就是说,m2需要来自m1的数据,m2需要来自m2的数据等等。此外,所需数据通常不仅特定于模型,而且在类型和数量上也是可变的。

不幸的是,数据m1x不是y的成员,因此,将base_model作为参数传递给m1也不会有效。

所以我有以下问题:

  1. 有没有更好的方法来设计这些对象,以便我可以轻松地在它们之间传递数据?在这里展望,有一些建议,最好的办法是重新设计对象,使它们之间的交互不是循环的。但是,这是必要的!

  2. 我是否必须为每个可能在对象之间共享的数据写一个“getter”?这似乎是很多编码(我有很多可能共享的数据)。然而,这似乎也相当复杂,因为“getter”(特定于一段数据)也必须满足抽象接口。

  3. 在像Python这样的高级语言中,这很简单,因为我们可以简单地创建一个新的数据类型作为模型的组合,但据我所知,这在Fortran中是不可能的。

    提前致谢。非常感谢任何帮助。

    编辑:在下面与francescalus讨论后,m2是一个选项。实际上,在上面给出的简单示例中,select type将是一个不错的选择。但是,在我的实际代码中,这将导致大型嵌套select type,所以如果有办法在不使用select type的情况下执行此操作,我会更喜欢它。感谢francescalus指出我关于select type的错误。

2 个答案:

答案 0 :(得分:3)

回答你的两个问题:

有更好的设计方法吗?

我不太清楚为什么你的设计中有这么多的约束,但简而言之是。您可以为模型使用上下文管理器。我建议您查看以下答案:Context class pattern

您是否必须在每个型号上编写一个getter方法?

不完全是,如果您在此特定问题上使用上下文策略,那么您需要在每个模型上实现的唯一方法是在模型之间共享数据的setter方法。

我在Python中为这个场景实现了一个有效的解决方案。代码比言辞更响亮。我避免使用Python的任何特殊功能,让您清楚地了解如何在这种情况下使用上下文。

from abc import ABC, abstractmethod
import random

class BaseModel(ABC):
    def __init__(self, ctx):
        super().__init__()
        self.ctx = ctx
        print("BaseModel initializing with context id:", ctx.getId())

    @abstractmethod
    def solveMe():
        pass

class BaseControl(object):
    # m1 - m3 could be replaced here with *args
    def __init__(self, m1, m2, m3):
        super().__init__()
        self.models = [m1, m2, m3]


class Control(BaseControl):
    def __init__(self, m1, m2, m3):
        super().__init__(m1, m2, m3)

    def run(self):
        print("Now Solving..")
        for m in self.models:
            print("Class: {} reports value: {}".format(type(m).__name__, m.solveMe()))


class Model1(BaseModel):
    def __init__(self, x, y, ctx):
        super().__init__(ctx)
        self.x = x
        self.y = y
        ctx.setVal("x", x)
        ctx.setVal("y", y)

    def solveMe(self):
        return self.x * self.y

class Model2(BaseModel):
    def __init__(self, z, ctx):
        super().__init__(ctx)
        self.z = z
        ctx.setVal("z", z)

    def solveMe(self):
        return self.z * self.ctx.getVal("x")

class Model3(BaseModel):
    def __init__(self, z, ctx):
        super().__init__(ctx)
        self.z = z
        ctx.setVal("z", z)

    def solveMe(self):
        return self.z * self.ctx.getVal("y")

class Context(object):
    def __init__(self):
        self.modelData = {}
        self.ctxId = random.getrandbits(32)

    def getVal(self, key):
        return self.modelData[key]

    def setVal(self, key, val):
        self.modelData[key] = val

    def getId(self):
        return self.ctxId


ctx = Context()

m1 = Model1(1,2, ctx)
m2 = Model2(4, ctx)
m3 = Model3(6, ctx)

# note that the order in the arguments to control defines behavior
control = Control(m1, m2, m3)
control.run()

输出

python context.py
BaseModel initializing with context id: 1236512420
BaseModel initializing with context id: 1236512420
BaseModel initializing with context id: 1236512420
Now Solving..
Class: Model1 reports value: 2
Class: Model2 reports value: 4
Class: Model3 reports value: 12

<强>解释

简而言之,我们创建了一个上下文类,它具有可以在不同模型之间共享的字典。此实现非常特定于您提供的原始数据类型(I.E. x,y,z)。如果您需要在模型之间共享数据之前计算数据,您仍然可以通过将solveMe()的返回值替换为延迟的承诺来使用此模式。

答案 1 :(得分:2)

FWIW,下面是基于键/值(*)访问其他对象的字段的类似尝试。为简单起见,主程序从child1_t对象获取一个整数,并将复数值设置为child2_t对象(两者都是parent_t的扩展类型)。

parent.f90:

module parent_m
    implicit none

    type, abstract :: parent_t   !(abstract is optional)
    contains
        procedure :: set
        procedure :: get
        procedure :: show
    endtype

    type composite_t
        class(parent_t), allocatable :: pa, pb
    endtype

contains
    subroutine set( this, key, val )  ! key-based setter
        class(parent_t), intent(inout) :: this
        character(*),   intent(in)     :: key
        integer,        intent(in)     :: val
    endsubroutine

    subroutine get( this, key, val )  ! key-based getter
        class(parent_t), intent(in)  :: this
        character(*),    intent(in)  :: key
        integer,         intent(out) :: val
    endsubroutine

    subroutine show( this )   ! print contents
        class(parent_t), intent(in) :: this
    endsubroutine
end module

child.f90:

module child_m
    use parent_m, only: parent_t
    implicit none

    type, extends(parent_t) :: child1_t
        integer :: n1 = 777   ! some property
    contains
        procedure :: get  => child1_get
        procedure :: show => child1_show
    endtype

    type, extends(parent_t) :: child2_t
        complex :: c2 = ( 0.0, 0.0 )   ! another property
    contains
        procedure :: set  => child2_set
        procedure :: show => child2_show
    endtype

contains

    subroutine child1_get( this, key, val )
        class(child1_t), intent(in)  :: this
        character(*),    intent(in)  :: key
        integer,         intent(out) :: val

        select case( key )
            case ( "num", "n1" ) ; val = this % n1  ! get n1
            case default ; stop "invalid key"
        end select
    end subroutine

    subroutine child1_show( this )
        class(child1_t), intent(in) :: this
        print *, "[child1] ", this % n1
    endsubroutine

    subroutine child2_set( this, key, val )
        class(child2_t), intent(inout) :: this
        character(*),    intent(in)    :: key
        integer,         intent(in)    :: val

        select case( key )
            case ( "coeff", "c2" ) ; this % c2 = cmplx( real(val), 0.0 )  ! set c2
            case default ; stop "invalid key"
        end select
    end subroutine

    subroutine child2_show( this )
        class(child2_t), intent(in) :: this
        print *, "[child2] ", this % c2
    endsubroutine

end module

main.f90时:

program main
    use parent_m, only: composite_t
    use child_m,  only: child1_t, child2_t
    implicit none
    type(composite_t) :: c
    integer itmp

    allocate( child1_t :: c % pa )
    allocate( child2_t :: c % pb )

    print *, "initial state:"
    call c % pa % show()
    call c % pb % show()

    call c % pa % get( "num",  itmp )   ! get an integer value from pa
    call c % pb % set( "coeff", itmp )  ! set a complex value to pb

    print *, "modified state:"
    call c % pa % show()
    call c % pb % show()
end

编译&amp;结果:

 $ gfortran parent.f90 child.f90 main.f90

 initial state:
 [child1]          777
 [child2]              (0.00000000,0.00000000)
 modified state:
 [child1]          777
 [child2]              (777.000000,0.00000000)

虽然上面的代码只处理整数和复数的“数据传输”,但是可以在select case构造中类似地添加其他类型的数据(不为每个添加新的getter / setter方法)。此外,如有必要,我们可以通过get()关键字为不同类型的set()(例如valueset_int())重载set_real()generic(在parent_t类型中),并在扩展类型中覆盖它们。同样也适用于数组类型的value,也许......

如果扩展类型之间的数据传输(复制)很昂贵(例如,大型数组),我想getter可以返回指向它们的指针,以便childX_t可以以紧密耦合的方式进行通信(没有知道他们的实施。)

(但我想可能有一个比上面做的更简单的方法,包括问题中的第1点(例如,重新设计程序本身)。另外,如果我们手头有一些字典类型,字典的使用(如the other answer)似乎对我更有吸引力。)