Intel® Fortran Compiler
Build applications that can scale for the future with optimized code designed for Intel® Xeon® and compatible processors.

Design Pattern in Fortran

velvia
Beginner
2,505 Views
Hi,
I write a finite volume code dealing with the flow of water in a porous media. For that, I have the following objects :
fluidProperties
This object contains the arrays (on value for each finite volume) of pressure and a lot of quantities that depends on pressure (like density, viscosity, etc..).
rockProperties
This object contains properties for the rock.
Obviously, one need to solve an equation where we need to have access to the value of these arrays in both objects. For the time being, I have a lot of getters to retrieve the values, but it seems that using too many getters is always a sign of bad software design. Does any one of you have something better to propose ?
Best regards,
Francois
0 Kudos
8 Replies
jimdempseyatthecove
Honored Contributor III
2,505 Views
Providing some sample code with type information might be useful.

Jim Dempsey
0 Kudos
velvia
Beginner
2,505 Views
Here is an example :
[fortran]module fluidProperties_class
    implicit none
    private

    public :: fluidProperties
    type fluidProperties
        private
        real, allocatable, dimension(:) :: p
        real, allocatable, dimension(:) :: rho
        real, allocatable, dimension(:) :: mu
    contains
        procedure, public :: init =>  init_fluidProperties
        procedure, public :: getP => getP_fluidProperties
        procedure, public :: getRho => getRho_fluidProperties
        procedure, public :: getMu => getMu_fluidProperties
    end type fluidProperties
contains
    subroutine init_fluidProperties(this, size, pressure, density, viscosity)
        class(fluidProperties) :: this
        integer, intent(in) :: size
        real, intent(in) :: pressure, density, viscosity

        allocate(this%p(1:size))
        allocate(this%rho(1:size))
        allocate(this%mu(1:size))
        this%p = pressure
        this%rho = density
        this%mu = viscosity
    end subroutine init_fluidProperties

    function getP_fluidProperties(this, k)
        class(fluidProperties) :: this
        integer, intent(in) :: k
        real :: getP_fluidProperties

        getP_fluidProperties = this%p(k)
    end function getP_fluidProperties

    function getRho_fluidProperties(this, k)
        class(fluidProperties) :: this
        integer, intent(in) :: k
        real :: getRho_fluidProperties

        getRho_fluidProperties = this%rho(k)
    end function getRho_fluidProperties

    function getMu_fluidProperties(this, k)
        class(fluidProperties) :: this
        integer, intent(in) :: k
        real :: getMu_fluidProperties

        getMu_fluidProperties = this%mu(k)
    end function getMu_fluidProperties
end module fluidProperties_class


module rockProperties_class
    implicit none
    private

    public :: rockProperties
    type rockProperties
        private
        real, allocatable, dimension(:) :: phi
        real, allocatable, dimension(:) :: k
    contains
        procedure, public :: init => init_rockProperties
        procedure, public :: getPhi => getPhi_rockProperties
        procedure, public :: getK => getK_rockProperties
    end type rockProperties
contains
    subroutine init_rockProperties(this, size, porosity, permeability)
        class(rockProperties) :: this
        integer, intent(in) :: size
        real, intent(in) :: porosity, permeability

        allocate(this%phi(1:size))
        allocate(this%k(1:size))
        this%phi = porosity
        this%k = permeability
    end subroutine init_rockProperties

    function getPhi_rockProperties(this, k)
        class(rockProperties) :: this
        integer, intent(in) :: k
        real :: getPhi_rockProperties

        getPhi_rockProperties = this%phi(k)
    end function getPhi_rockProperties

    function getK_rockProperties(this, k)
        class(rockProperties) :: this
        integer, intent(in) :: k
        real :: getK_rockProperties

        getK_rockProperties = this%k(k)
    end function getK_rockProperties
end module rockProperties_class

module equation_class
    use rockProperties_class
    implicit none
    private

    public :: equation
    type equation
        private
        integer :: size
        type(rockProperties) :: myRockProperties

    contains
        procedure, public :: init => init_equation
        procedure, public :: computeSum => computeSum_equation
    end type equation
contains
    subroutine init_equation(this, size)
        class(equation) :: this
        integer, intent(in) :: size

        call this%myRockProperties%init(size, 0.3, 1.0e-12)
        this%size = size
    end subroutine init_equation

    function computeSum_equation(this, myFluidProperties)
        use fluidProperties_class
        class(equation) :: this
        class(fluidProperties), intent(in) :: myFluidProperties
        integer :: k
        real :: sum, computeSum_equation

        sum = 0.0
        do k = 1, this%size
            sum = sum + this%myRockProperties%getK(k) * myFluidProperties%getP(k)
        end do
        computeSum_equation = sum
    end function computeSum_equation
end module equation_class

program main
    use fluidProperties_class
    use equation_class
    implicit none

    integer :: nbCells = 10
    type(equation) :: myEquation
    type(fluidProperties) :: myFluidProperties

    call myFluidProperties%init(nbCells, 1.0e+5, 1000.0, 5.0e-4)
    call myEquation%init(nbCells)

    write (*,'(es15.5)') myEquation%computeSum(myFluidProperties)
end program main[/fortran]
0 Kudos
mriedman
Novice
2,505 Views
We have a program that is written in a very similar style, using a UML tool which generates part of the code, especially getters and setters,so I know your issue very well.

One thing that can be done is to declare the most frequently used arrays public instead of private. That allows direct access from outside. Obviously this breaksthe OO law. But it will get you better performance.

The other thing to try for that sort of code is interprocedural optimization (-ipo) which allows the compiler to do inlining across source file boundaries. That can reduce or eliminate the usual overhead that comes with getters.

In general I think that performance critical components like solvers should not be writtenthat style. For such codes OO does not add a lot of valueand isexpensive from the performance perspective. For GUI stuff that's certainly o.k.
0 Kudos
velvia
Beginner
2,505 Views
mriedman,
Thanks for your reply. I have a few questions :
- Performance : I don't really understand this issue. I agree that making a variable public will give you a direct access to the components of the vector, eliminating the need for a getter. But if you write a getter, I am sure that ifort is going to inline it if you use interprocedural optimization, and you will get the same performance.
- UML Tool : Do you have such a tool that generates Fortran ?
Francois
0 Kudos
mriedman
Novice
2,505 Views
Performance: Your are right about -ipo. But most developers don't use aggressive optimizations like this. Most Fortran applications Ihave seenare built with only -O. In fact that is mostly sufficient for classic F77 style codes.

UML Tool: We use Objecteering. Some folks say that we are the only customer using Objecteering with (customized) F95 code generation. In fact they don't advertize it, but we have it and it's supported.

Michael
0 Kudos
jimdempseyatthecove
Honored Contributor III
2,505 Views
I agree with mriedman, there is no advantage in keeping the arrays private with get/put member functions (at least as indicated from your sketch code). Then your sample sum subroutine could directly use the arrays and presumably take advantage of SSE/AVX.

Jim Dempsey
0 Kudos
velvia
Beginner
2,505 Views
Jim,
Do you mean that the compiler can't vectorize things if we use the getters ?
Francois
0 Kudos
jimdempseyatthecove
Honored Contributor III
2,505 Views
IPO should be able to do inlining provided that the source to your modules is available. As to if inlineing makes the code vectorizable this may depend upon if your functions are PURE and ELEMENTAL.

Try

!DEC$ ATTRIBUTES INLINE :: Getter
PURE ELEMENTAL Function Getter(this,i)
type(YourType) :: this
REAL :: Getter
integer :: i
Getter = this%YourArray(i)
end Function Getter

*** Untested code ***

Use IPO with optimization (i.e. not Debug build)

Jim Dempsey
0 Kudos
Reply