sfilippone / psblas3

A library of parallel sparse linear algebra on high performance computer.
Other
56 stars 12 forks source link

Abstract derived types? #16

Closed ShatrovOA closed 2 years ago

ShatrovOA commented 3 years ago

Dear @sfilippone !

Just found you psblas3 and amg4psblas. Looks great, but why don't you abstract classes? I believe it would be much easier to define abstract interface and make class that inherits abstract class to provide implementation, instead of writing functions that throw exceptions.. Compiler will error the incomplete derived type.

Best regards, Oleg

sfilippone commented 3 years ago

Hi, thanks for your interest, glad you liked psblas and amg4psblas.

Re abstract classes, in principle you are correct. However, there are a number of methods that are naturally split, in the sense that part of the implementation really belongs in the base class, and part in the derived class. This can be easily accomplished by having the derived class method invoke the base class method as in call this%base_type%foo() and then complete the work with the remaining details. Unfortunately, this cannot work if base_type is abstract, which is why I refrained from doing so.

I will revisit the choice again at the next major design iteration for version 4, where I plan to change quite a few things, and I will run through the various pros and cons again. In the meantime I have partially alleviated some of the work by defining "poor man's templates" for writing the codes, so I am gaining there.

If you have other observations and/or ideas, do not hesitate to let me know.

Cheers Salvatore

On Tue, Jun 29, 2021 at 10:27 PM Oleg Shatrov @.***> wrote:

Dear @sfilippone https://github.com/sfilippone !

Just found you psblas3 and amg4psblas. Looks great, but why don't you abstract classes? I believe it would be much easier to define abstract interface and make class that inherits abstract class to provide implementation, instead of writing functions that throw exceptions.. Compiler will error the incomplete derived type.

Best regards, Oleg

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/sfilippone/psblas3/issues/16, or unsubscribe https://github.com/notifications/unsubscribe-auth/AD274T3XOBAB5KDMRNCQS2LTVIUBLANCNFSM47Q6TUPQ .

ShatrovOA commented 3 years ago

Consider the following example:

module test_abstract
implicit none
private
public :: base, child

  type, abstract :: base
  contains
    procedure, nopass :: semi_implemented
    ! This will call 'semi_implemented'
    procedure(fully_implemented_interface), pass(self), deferred :: fully_implemented
  end type base

  abstract interface
    subroutine fully_implemented_interface(self)
      import :: base
      class(base), intent(in) :: self
    end subroutine fully_implemented_interface
  end interface

  type, extends(base) :: child
  contains
  procedure, pass(self) :: fully_implemented => child_fully_implemented
  end type child

  contains

  subroutine semi_implemented()

    print*,'This is base class'
  end subroutine semi_implemented

  subroutine child_fully_implemented(self)
    class(child), intent(in) :: self

    print*,'This is child class'
    call self%semi_implemented()
  end subroutine child_fully_implemented
end module test_abstract

program test
use test_abstract
type(child) :: c

  call c%semi_implemented()
  call c%fully_implemented()
end program test

This compiles and runs fine with gfortran-7. The result is:

 This is base class
 This is child class
 This is base class

Did you mean that this example won't work or I misunderstood something?

ShatrovOA commented 3 years ago

I also noticed that every time I will call "psb_krylov" it will allocate and deallocate memory. I believe that it can be very time consuming if you are planning to solve equation with similar matrix structure and different non-zero values couple million times. That is why I suggest that krylov methods should be derived types with at least 3 methods: init - allocate all necessary memory, solve - Solve preconditioned Ax=b, destroy - release all memory.

sfilippone commented 3 years ago

What I had in mind is this: module test_abstract implicit none private public :: base, child

type, abstract :: base contains ! This will call 'semi_implemented' procedure(fully_implemented_interface), pass(self) :: method => base_method end type base

end interface

type, extends(base) :: child contains procedure, pass(self) :: method=> child_method end type child

contains

subroutine base_method(self)

  class(base), intent(in) :: self
  ! Do the base thing
  print*,'This is base class'
end subroutine base_method

subroutine child_method(self)
  class(child), intent(in) :: self

  print*,'This is child class'
  call self%base%method()
  print*,'Now do the child thing'

end subroutine child_method

end module test_abstract

and this cannot work as written

On Wed, Jun 30, 2021 at 11:21 AM Oleg Shatrov @.***> wrote:

Consider the following example:

module test_abstractimplicit none private public :: base, child

type, abstract :: base contains procedure, nopass :: semi_implemented ! This will call 'semi_implemented' procedure(fully_implemented_interface), pass(self), deferred :: fully_implemented end type base

abstract interface subroutine fully_implemented_interface(self) import :: base class(base), intent(in) :: self end subroutine fully_implemented_interface end interface

type, extends(base) :: child contains procedure, pass(self) :: fully_implemented => child_fully_implemented end type child

contains

subroutine semi_implemented()

print*,'This is base class'

end subroutine semi_implemented

subroutine child_fully_implemented(self) class(child), intent(in) :: self

print*,'This is child class'
call self%semi_implemented()

end subroutine child_fully_implemented end module test_abstract

program test use test_abstract type(child) :: c

call c%semi_implemented() call c%fully_implemented()end program test

This compiles and runs fine with gfortran-7. The result is:

This is base class This is child class This is base class

Did you mean that this example won't work or I misunderstood something?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/sfilippone/psblas3/issues/16#issuecomment-871237784, or unsubscribe https://github.com/notifications/unsubscribe-auth/AD274T2MNBTONFADJ4ZY3Z3TVLOXZANCNFSM47Q6TUPQ .

sfilippone commented 3 years ago

There is a method in the amg4psblas "prec" object that will allocate/deallocate memory outside the Krylov method. However, on most Linux based systems, allocation and deallocation are very, very cheap. It is a serious issue when using GPUs, ,though, which is why I defined the allocate_wrk method for the preconditioner object.

On Wed, Jun 30, 2021 at 12:38 PM Oleg Shatrov @.***> wrote:

I also noticed that every time I will call "psb_krylov" it will allocate and deallocate memory. I believe that it can be very time consuming if you are planning to solve equation with similar matrix structure and different non-zero values couple million times. That is why I suggest that krylov methods should be derived types with at least 3 methods: init - allocate all necessary memory, solve - Solve preconditioned Ax=b, destroy - release all memory.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/sfilippone/psblas3/issues/16#issuecomment-871289938, or unsubscribe https://github.com/notifications/unsubscribe-auth/AD274TZ4TPBYHVFASZ6N563TVLXZBANCNFSM47Q6TUPQ .

ShatrovOA commented 3 years ago

This wont work for 2 reasons, even if base class wasn't abstract:

  1. You don't have object of "base" class in your child derived type, you inherit from it.
  2. By declaring "method => child_method" you override "base_method" with "child_method" and whenever you call "method" inside "child" class it will always call "child_method".

Here is the reproducer:

module test_abstract
implicit none
private
public :: base, child

  type :: base
  contains
    procedure, pass(self) :: method => base_method
  end type base

  type, extends(base) :: child
  contains
  procedure, pass(self) :: method => child_method
  end type child

  contains

  subroutine base_method(self)
    class(base), intent(in) :: self
    print*,'This is base class'
  end subroutine base_method

  subroutine child_method(self)
    class(child), intent(in) :: self

    print*,'This is child class'
    call self%method()
  end subroutine child_method
end module test_abstract

program test
use test_abstract
type(child) :: c

  call c%method()
end program test

Sample output:

...
 This is child class
 This is child class
 This is child class
 This is child class
 This is child class
 This is child class
 This is child class
 This is child class
 This is child class
 This is child class
[1]    37726 segmentation fault  ./a.out

This can easily handled with some naming convention. For example:

module test_abstract
implicit none
private
public :: base, child

  type, abstract :: base
  contains
    procedure, pass(self), non_overridable :: base_method
    procedure(method_interface), pass(self), deferred :: method
  end type base

  abstract interface
    subroutine method_interface(self)
      import :: base
      class(base), intent(in) :: self
    end subroutine method_interface
  end interface

  type, extends(base) :: child
  contains
    procedure, pass(self) :: method => child_method
  end type child

  contains

  subroutine base_method(self)
    class(base), intent(in) :: self
    print*,'This is base class'
  end subroutine base_method

  subroutine child_method(self)
    class(child), intent(in) :: self

    print*,'This is child class'
    call self%base_method()
  end subroutine child_method
end module test_abstract

program test
use test_abstract
type(child) :: c

  call c%method()
  call c%base_method()
end program test

I declared semi-implemented method as "base_method" and added keyword non_overridable. Real method goes as deferred and it can safely execute "base_method"

sfilippone commented 3 years ago

Metcalf, Reid and Cohen, "Modern Fortran explained", page 266: "Additionally an extended type has a parent component; this is a component that has the type and type parameters of the old type and its name is that of the old type ...... The parent component is particularly useful when invoking procedures that operate on the parent type .. "

On Wed, Jun 30, 2021 at 7:42 PM Oleg Shatrov @.***> wrote:

This wont work for 2 reasons, even if base class wasn't abstract:

  1. You don't have object of "base" class in your child derived type, you inherit from it.
  2. By declaring "method => child_method" you override "base_method" with "child_method" and whenever you call "method" inside "child" class it will always call "child_method".

Here is the reproducer:

module test_abstractimplicit none private public :: base, child

type :: base contains procedure, pass(self) :: method => base_method end type base

type, extends(base) :: child contains procedure, pass(self) :: method => child_method end type child

contains

subroutine base_method(self) class(base), intent(in) :: self print*,'This is base class' end subroutine base_method

subroutine child_method(self) class(child), intent(in) :: self

print*,'This is child class'
call self%method()

end subroutine child_method end module test_abstract

program test use test_abstract type(child) :: c

call c%method()end program test

Sample output:

... This is child class This is child class This is child class This is child class This is child class This is child class This is child class This is child class This is child class This is child class [1] 37726 segmentation fault ./a.out

This can easily handled with some naming convention. For example:

module test_abstractimplicit none private public :: base, child

type, abstract :: base contains procedure, pass(self), non_overridable :: base_method procedure(method_interface), pass(self), deferred :: method end type base

abstract interface subroutine method_interface(self) import :: base class(base), intent(in) :: self end subroutine method_interface end interface

type, extends(base) :: child contains procedure, pass(self) :: method => child_method end type child

contains

subroutine base_method(self) class(base), intent(in) :: self print*,'This is base class' end subroutine base_method

subroutine child_method(self) class(child), intent(in) :: self

print*,'This is child class'
call self%base_method()

end subroutine child_method end module test_abstract

program test use test_abstract type(child) :: c

call c%method() call c%base_method()end program test

I declared semi-implemented method as "base_method" and added keyword non_overridable. Real method goes as deferred and it can safely execute "base_method"

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/sfilippone/psblas3/issues/16#issuecomment-871603831, or unsubscribe https://github.com/notifications/unsubscribe-auth/AD274T26IQVELGOP5BD3TWTTVNJSBANCNFSM47Q6TUPQ .

sfilippone commented 3 years ago

Therefore in the overriding method you have a mechanism to invoke the overridden method. I use this in some places in the library (e.g. in the "clone" and transpose methods) S.

On Thu, Jul 1, 2021 at 8:59 AM Salvatore Filippone < @.***> wrote:

Metcalf, Reid and Cohen, "Modern Fortran explained", page 266: "Additionally an extended type has a parent component; this is a component that has the type and type parameters of the old type and its name is that of the old type ...... The parent component is particularly useful when invoking procedures that operate on the parent type .. "

On Wed, Jun 30, 2021 at 7:42 PM Oleg Shatrov @.***> wrote:

This wont work for 2 reasons, even if base class wasn't abstract:

  1. You don't have object of "base" class in your child derived type, you inherit from it.
  2. By declaring "method => child_method" you override "base_method" with "child_method" and whenever you call "method" inside "child" class it will always call "child_method".

Here is the reproducer:

module test_abstractimplicit none private public :: base, child

type :: base contains procedure, pass(self) :: method => base_method end type base

type, extends(base) :: child contains procedure, pass(self) :: method => child_method end type child

contains

subroutine base_method(self) class(base), intent(in) :: self print*,'This is base class' end subroutine base_method

subroutine child_method(self) class(child), intent(in) :: self

print*,'This is child class'
call self%method()

end subroutine child_method end module test_abstract

program test use test_abstract type(child) :: c

call c%method()end program test

Sample output:

... This is child class This is child class This is child class This is child class This is child class This is child class This is child class This is child class This is child class This is child class [1] 37726 segmentation fault ./a.out

This can easily handled with some naming convention. For example:

module test_abstractimplicit none private public :: base, child

type, abstract :: base contains procedure, pass(self), non_overridable :: base_method procedure(method_interface), pass(self), deferred :: method end type base

abstract interface subroutine method_interface(self) import :: base class(base), intent(in) :: self end subroutine method_interface end interface

type, extends(base) :: child contains procedure, pass(self) :: method => child_method end type child

contains

subroutine base_method(self) class(base), intent(in) :: self print*,'This is base class' end subroutine base_method

subroutine child_method(self) class(child), intent(in) :: self

print*,'This is child class'
call self%base_method()

end subroutine child_method end module test_abstract

program test use test_abstract type(child) :: c

call c%method() call c%base_method()end program test

I declared semi-implemented method as "base_method" and added keyword non_overridable. Real method goes as deferred and it can safely execute "base_method"

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/sfilippone/psblas3/issues/16#issuecomment-871603831, or unsubscribe https://github.com/notifications/unsubscribe-auth/AD274T26IQVELGOP5BD3TWTTVNJSBANCNFSM47Q6TUPQ .

sfilippone commented 3 years ago

Book version of 2011 section 14.2 type extension

On Thu, Jul 1, 2021 at 8:59 AM Salvatore Filippone < @.***> wrote:

Metcalf, Reid and Cohen, "Modern Fortran explained", page 266: "Additionally an extended type has a parent component; this is a component that has the type and type parameters of the old type and its name is that of the old type ...... The parent component is particularly useful when invoking procedures that operate on the parent type .. "

On Wed, Jun 30, 2021 at 7:42 PM Oleg Shatrov @.***> wrote:

This wont work for 2 reasons, even if base class wasn't abstract:

  1. You don't have object of "base" class in your child derived type, you inherit from it.
  2. By declaring "method => child_method" you override "base_method" with "child_method" and whenever you call "method" inside "child" class it will always call "child_method".

Here is the reproducer:

module test_abstractimplicit none private public :: base, child

type :: base contains procedure, pass(self) :: method => base_method end type base

type, extends(base) :: child contains procedure, pass(self) :: method => child_method end type child

contains

subroutine base_method(self) class(base), intent(in) :: self print*,'This is base class' end subroutine base_method

subroutine child_method(self) class(child), intent(in) :: self

print*,'This is child class'
call self%method()

end subroutine child_method end module test_abstract

program test use test_abstract type(child) :: c

call c%method()end program test

Sample output:

... This is child class This is child class This is child class This is child class This is child class This is child class This is child class This is child class This is child class This is child class [1] 37726 segmentation fault ./a.out

This can easily handled with some naming convention. For example:

module test_abstractimplicit none private public :: base, child

type, abstract :: base contains procedure, pass(self), non_overridable :: base_method procedure(method_interface), pass(self), deferred :: method end type base

abstract interface subroutine method_interface(self) import :: base class(base), intent(in) :: self end subroutine method_interface end interface

type, extends(base) :: child contains procedure, pass(self) :: method => child_method end type child

contains

subroutine base_method(self) class(base), intent(in) :: self print*,'This is base class' end subroutine base_method

subroutine child_method(self) class(child), intent(in) :: self

print*,'This is child class'
call self%base_method()

end subroutine child_method end module test_abstract

program test use test_abstract type(child) :: c

call c%method() call c%base_method()end program test

I declared semi-implemented method as "base_method" and added keyword non_overridable. Real method goes as deferred and it can safely execute "base_method"

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/sfilippone/psblas3/issues/16#issuecomment-871603831, or unsubscribe https://github.com/notifications/unsubscribe-auth/AD274T26IQVELGOP5BD3TWTTVNJSBANCNFSM47Q6TUPQ .

ShatrovOA commented 3 years ago

Wow, did not know this was a thing.. Thanks for info =)

sfilippone commented 3 years ago

Yep, life (and language standards) are full of surprises :-)

On Thu, Jul 1, 2021 at 1:30 PM Oleg Shatrov @.***> wrote:

Wow, did not know this was a thing.. Thanks for info =)

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/sfilippone/psblas3/issues/16#issuecomment-872165970, or unsubscribe https://github.com/notifications/unsubscribe-auth/AD274T22JAISEMJJOFYFTKTTVRGVVANCNFSM47Q6TUPQ .