Fortran operator overloading for complex number matrix type

Viewed 132

How can overload operator(+) and assigment(-) for my class?

the compiler shows me the following message

Error: Component to the right of a part reference with nonzero rank must not have the ALLOCATABLE 
attribute at (1).

For the assigment(=), I have no idea how to do it. For c ++ it was easier. Return pointner this

function zmat_zmat_add(zmatrix1,zmatrix2) result(res_zmat_zmat)
    type(zmatrix_type), dimension(:,:), intent(in) :: zmatrix1
    type(zmatrix_type), dimension(:,:), intent(in) :: zmatrix2
    type(zmatrix_type) :: res_zmat_zmat
    integer :: rows
    integer :: i,j ! liczniki pętli
    rows=3
    
    do i=1, rows
        do j=1, rows
        res_zmat_zmat%zmatrix_data(i,j)%realis= &
         zmatrix1%zmatrix_data(i,j)%realis + zmatrix2%zmatrix_data(i,j)%realis
        res_zmat_zmat%zmatrix_data(i,j)%imaginalis = &
         zmatrix1%zmatrix_data(i,j)%imaginalis + &
          zmatrix2%zmatrix_data(i,j)%imaginalis
       enddo
    enddo
    
end function zmat_zmat_add

rest code

module zmatrix_module
implicit none
type, public :: zcomplex_type

    real :: realis
    real :: imaginalis

end type zcomplex_type

type, extends(zcomplex_type), public :: zmatrix_type
    type(zcomplex_type), dimension(:,:), allocatable, public :: zmatrix_data
end type zmatrix_type

    public :: zmatrix_allocate
    public :: zmatrix_free
    public :: zmatrix_set
    public :: zmatrix_print

interface operator(+)
procedure zzadd
procedure zmat_zmat_add
 end interface

contains

function zzadd(z1,z2) result(res)
type(zcomplex_type), intent(in) :: z1
type(zcomplex_type), intent(in) :: z2
type(zcomplex_type)  :: res
res%realis=z1%realis+z2%realis 
res%imaginalis= z1%imaginalis +z2%imaginalis
end function zzadd


function zmat_zmat_add(zmatrix1,zmatrix2) result(res_zmat_zmat)
    type(zmatrix_type), dimension(:,:), intent(in) :: zmatrix1
    type(zmatrix_type), dimension(:,:), intent(in) :: zmatrix2
    type(zmatrix_type) :: res_zmat_zmat
    integer :: rows
    integer :: i,j 
    rows=3
    
    do i=1, rows
        do j=1, rows
        res_zmat_zmat%zmatrix_data(i,j)%realis= &
         zmatrix1%zmatrix_data(i,j)%realis + zmatrix2%zmatrix_data(i,j)%realis
        res_zmat_zmat%zmatrix_data(i,j)%imaginalis = &
         zmatrix1%zmatrix_data(i,j)%imaginalis + &
          zmatrix2%zmatrix_data(i,j)%imaginalis
       enddo
    enddo
    
end function zmat_zmat_add

subroutine zmatrix_allocate(zarray,rows)
    type(zmatrix_type), intent(out) :: zarray
    integer, intent(in) :: rows
    allocate(zarray%zmatrix_data(1:rows, 1:rows))
end subroutine zmatrix_allocate

subroutine zmatrix_free(zarray)
    type(zmatrix_type), intent(inout) :: zarray
    deallocate(zarray%zmatrix_data)
end subroutine zmatrix_free



subroutine zmatrix_set(zarray, rows, re_values, im_values)
    type(zmatrix_type), intent(inout) :: zarray
    integer, intent(in) :: rows
    real, intent(in) :: re_values, im_values
    integer :: i,j
    do i=1, rows
        do j=1, rows
            zarray%zmatrix_data(i,j)%realis = re_values
            zarray%zmatrix_data(i,j)%imaginalis = im_values
        enddo
    enddo
end subroutine zmatrix_set

subroutine zmatrix_print(array,rows)
    type(zmatrix_type), intent(in) :: array
    integer, intent(in) :: rows
    integer i,j
    
    do i=1, rows
        write(*,*) (array%zmatrix_data(i,j), j=1, rows)
    enddo
    write(*,*)
end subroutine zmatrix_print

end module zmatrix_module

Program main
use zmatrix_module
implicit none

type(zmatrix_type) :: mat1
type(zmatrix_type) :: mat2
type(zmatrix_type) :: mat3
type(zcomplex_type) :: z1
type(zcomplex_type) :: z2
type(zcomplex_type) :: z3
integer :: rows
rows=2
print *, " AAAAAAA"
call zmatrix_allocate(mat1,rows)

call zmatrix_set(mat1,rows,10.0,8.0)

call zmatrix_print(mat1,rows)

print *, "BBBBBBBB" 
call zmatrix_allocate(mat2,rows)

call zmatrix_set(mat2,rows,1.0,2.0)

call zmatrix_print(mat2,rows)

print *, "CCCCCC"
call zmatrix_allocate(mat3,rows)

mat3=zmat_zmat_add(mat1,mat2)
mat3=mat1+mat2
call zmatrix_print(mat3,rows)

call zmatrix_free(mat1)
call zmatrix_free(mat2)
call zmatrix_free(mat3)
End Program main
2 Answers

The comments point out the immediate problem - you don't need the dimension attribute in the zmat_zmat_add routine - you are adding a single matrix to another matrix, not an array of matrix to another array of matrices. Thus you have a scalar of the appropriate type for each dummy argument.

However as the actual question indicates there is a second problem, how to allocate the result array for zmat_zmat_add. Well, you make the result allocatable and allocate it! I've shown in the first code below the most direct way to solve the problems you are showing. However the code you have written reads a bit like writing C++ as Fortran. This is not the best way to solve this problem, and so I have provided a second solution which is a much more Fortran way of doing things. This is below the first code. Anyway here the quick and dirty fix to your code:

ijb@ijb-Latitude-5410:~/work/stack$ cat zm1.f90
module zmatrix_module
implicit none
type, public :: zcomplex_type

    real :: realis
    real :: imaginalis

end type zcomplex_type

type, extends(zcomplex_type), public :: zmatrix_type
    type(zcomplex_type), dimension(:,:), allocatable, public :: zmatrix_data
end type zmatrix_type

    public :: zmatrix_allocate
    public :: zmatrix_free
    public :: zmatrix_set
    public :: zmatrix_print

interface operator(+)
procedure zzadd
procedure zmat_zmat_add
 end interface

contains

function zzadd(z1,z2) result(res)
type(zcomplex_type), intent(in) :: z1
type(zcomplex_type), intent(in) :: z2
type(zcomplex_type)  :: res
res%realis=z1%realis+z2%realis 
res%imaginalis= z1%imaginalis +z2%imaginalis
end function zzadd


function zmat_zmat_add(zmatrix1,zmatrix2) result(res_zmat_zmat)
    type(zmatrix_type), intent(in) :: zmatrix1
    type(zmatrix_type), intent(in) :: zmatrix2
    type(zmatrix_type) :: res_zmat_zmat
    integer :: cols, rows
    integer :: i,j 

    rows = Size( zmatrix1%zmatrix_data, Dim = 1 )
    cols = Size( zmatrix1%zmatrix_data, Dim = 2 )

    Allocate( res_zmat_zmat%zmatrix_data( 1:rows, 1:cols ) )
    
    do i=1, rows
        do j=1, cols
        res_zmat_zmat%zmatrix_data(i,j)%realis= &
         zmatrix1%zmatrix_data(i,j)%realis + zmatrix2%zmatrix_data(i,j)%realis
        res_zmat_zmat%zmatrix_data(i,j)%imaginalis = &
         zmatrix1%zmatrix_data(i,j)%imaginalis + &
          zmatrix2%zmatrix_data(i,j)%imaginalis
       enddo
    enddo
    
end function zmat_zmat_add

subroutine zmatrix_allocate(zarray,rows)
    type(zmatrix_type), intent(out) :: zarray
    integer, intent(in) :: rows
    allocate(zarray%zmatrix_data(1:rows, 1:rows))
end subroutine zmatrix_allocate

subroutine zmatrix_free(zarray)
    type(zmatrix_type), intent(inout) :: zarray
    deallocate(zarray%zmatrix_data)
end subroutine zmatrix_free



subroutine zmatrix_set(zarray, rows, re_values, im_values)
    type(zmatrix_type), intent(inout) :: zarray
    integer, intent(in) :: rows
    real, intent(in) :: re_values, im_values
    integer :: i,j
    do i=1, rows
        do j=1, rows
            zarray%zmatrix_data(i,j)%realis = re_values
            zarray%zmatrix_data(i,j)%imaginalis = im_values
        enddo
    enddo
end subroutine zmatrix_set

subroutine zmatrix_print(array,rows)
    type(zmatrix_type), intent(in) :: array
    integer, intent(in) :: rows
    integer i,j
    
    do i=1, rows
        write(*,*) (array%zmatrix_data(i,j), j=1, rows)
    enddo
    write(*,*)
end subroutine zmatrix_print

end module zmatrix_module

Program main
use zmatrix_module
implicit none

type(zmatrix_type) :: mat1
type(zmatrix_type) :: mat2
type(zmatrix_type) :: mat3
integer :: rows
rows=2
print *, " AAAAAAA"
call zmatrix_allocate(mat1,rows)

call zmatrix_set(mat1,rows,10.0,8.0)

call zmatrix_print(mat1,rows)

print *, "BBBBBBBB" 
call zmatrix_allocate(mat2,rows)

call zmatrix_set(mat2,rows,1.0,2.0)

call zmatrix_print(mat2,rows)

print *, "CCCCCC"
call zmatrix_allocate(mat3,rows)

mat3=zmat_zmat_add(mat1,mat2)
mat3=mat1+mat2
call zmatrix_print(mat3,rows)

call zmatrix_free(mat1)
call zmatrix_free(mat2)
call zmatrix_free(mat3)
End Program main

ijb@ijb-Latitude-5410:~/work/stack$ gfortran --version
GNU Fortran (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

ijb@ijb-Latitude-5410:~/work/stack$ gfortran -std=f2018 -Wall -Wextra -fcheck=all -O -Wuse-without-only zm1.f90 -o zm1
zm1.f90:99:4:

   99 | use zmatrix_module
      |    1
Warning: USE statement at (1) has no ONLY qualifier [-Wuse-without-only]
ijb@ijb-Latitude-5410:~/work/stack$ ./zm1
  AAAAAAA
   10.0000000       8.00000000       10.0000000       8.00000000    
   10.0000000       8.00000000       10.0000000       8.00000000    

 BBBBBBBB
   1.00000000       2.00000000       1.00000000       2.00000000    
   1.00000000       2.00000000       1.00000000       2.00000000    

 CCCCCC
   11.0000000       10.0000000       11.0000000       10.0000000    
   11.0000000       10.0000000       11.0000000       10.0000000    

ijb@ijb-Latitude-5410:~/work/stack$ 

As I say this is not a very "Fortran" way of solving this. Here is what I would do. Note

  • The intrinsic Complex data type
  • Array syntax to simplify the code
  • Allocation of allocatable arrays when they are the result of a calculation, again simplifying the code
  • Use of intrinsics to find properties of arrays rather than carrying around extra variables which contain duplicate information
  • Type bound procedures
  • (Not really only Fortran but use of private for encapsulation and to minimise name space pollution)
  • Quite probably others

Anyway here goes

Module zmatrix_module
  
  Implicit None

  Type, Public :: zmatrix_type
     Private
     Complex, Dimension(:,:), Allocatable, Private :: zmatrix_data
   Contains
     Procedure, Public  :: allocate      => zmatrix_allocate
     Procedure, Public  :: free          => zmatrix_free
     Procedure, Public  :: set           => zmatrix_set
     Procedure, Public  :: print         => zmatrix_print
     Generic  , Public  :: Operator( + ) => add
     Procedure, Private :: add           => zmat_zmat_add
  End Type zmatrix_type

  Private

Contains

  Function zmat_zmat_add(zmatrix1,zmatrix2) Result(res_zmat_zmat)

    Class(zmatrix_type), Intent(in) :: zmatrix1
    Type (zmatrix_type), Intent(in) :: zmatrix2

    Type (zmatrix_type) :: res_zmat_zmat

    ! Uses allocation on assignment
    ! Also use array syntax to simplify code
    res_zmat_zmat%zmatrix_data = zmatrix1%zmatrix_data + zmatrix2%zmatrix_data

  End Function zmat_zmat_add

  Subroutine zmatrix_allocate(zarray,rows)
    ! Note Intent(out) ensures the array is deallocate on entry to the routine
    Class(zmatrix_type), Intent(out) :: zarray
    Integer, Intent(in) :: rows
    Allocate(zarray%zmatrix_data(1:rows, 1:rows))
  End Subroutine zmatrix_allocate

  Subroutine zmatrix_free(zarray)
    Class(zmatrix_type), Intent(inout) :: zarray
    Deallocate(zarray%zmatrix_data)
  End Subroutine zmatrix_free

  Subroutine zmatrix_set(zarray, values )
    Class(zmatrix_type), Intent(inout) :: zarray
    Complex, Intent(in) :: values
    zarray%zmatrix_data = values
  End Subroutine zmatrix_set

  Subroutine zmatrix_print(array)
    Class(zmatrix_type), Intent(in) :: array

    Integer :: i

    ! Don't need to carry around extra data, just ask the array its size
    Do i=1, Size( array%zmatrix_data, Dim = 1 )
       Write(*,*) array%zmatrix_data(i,:)
    Enddo
    Write(*,*)
  End Subroutine zmatrix_print

End Module zmatrix_module

Program main
  
  Use zmatrix_module, Only : zmatrix_type
  
  Implicit None

  Type( zmatrix_type ) :: mat1
  Type( zmatrix_type ) :: mat2
  Type( zmatrix_type ) :: mat3
  
  Integer :: rows
  
  rows=2
  
  Print *, " AAAAAAA"
  Call mat1%allocate( rows )

  Call mat1%set( ( 10.0, 8.0 ) )

  Call mat1%print()

  Print *, "BBBBBBBB" 
  Call mat2%allocate( rows )

  Call mat2%set( ( 1.0, 2.0 ) )

  Call mat2%print()

  Print *, "CCCCCC"
  ! Note mat3 gets auto-allocated as a result of the operation
  mat3 = mat1 + mat2
  Call mat3%print()

  Call mat3%free()
  Call mat2%free()
  Call mat1%free()

End Program main

ijb@ijb-Latitude-5410:~/work/stack$ gfortran --version
GNU Fortran (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

ijb@ijb-Latitude-5410:~/work/stack$ gfortran -std=f2018 -Wall -Wextra -fcheck=all -O -Wuse-without-only zm2.f90 -o zm2
ijb@ijb-Latitude-5410:~/work/stack$ ./zm2
  AAAAAAA
             (10.0000000,8.00000000)             (10.0000000,8.00000000)
             (10.0000000,8.00000000)             (10.0000000,8.00000000)

 BBBBBBBB
             (1.00000000,2.00000000)             (1.00000000,2.00000000)
             (1.00000000,2.00000000)             (1.00000000,2.00000000)

 CCCCCC
             (11.0000000,10.0000000)             (11.0000000,10.0000000)
             (11.0000000,10.0000000)             (11.0000000,10.0000000)

ijb@ijb-Latitude-5410:~/work/stack$ 

There is another way which would fit this nicely, namely parametrised derived types. But I have no experience here so I won't go into something I don't know.

@Ian Bush mentioned in his answer that another option is to use the feature called parameterized derived types.

I will show a sample implementation here using those type parameters. Here are some callouts, first:

  • With this you might reduce the need for allocatable variables, because you can specify the shape of your type-members at compile-time or runtime.
  • If you want to learn more about parameterized types, this article is a good starting point.
  • This was introduced in Fortran 2003. The availability of this feature depends on the compiler. Even some compilers that claim to be fully compliant with Fortran 2003 might fail to compile (for example, gfortran fails in several points of the following code).

Sample implementation:

module zmatrix_module
  implicit none
  private

  ! A parameterized-type is declared like this.
  type, public :: zmatrix(rows)
    ! Delcare each parameter inside the type. In this case it is a len-type.
    integer, len :: rows                        
    private
    ! You can use a len-type parameter in initialization expressions.
    complex :: data(rows, rows)
  contains
    ! No need for `allocate` and `free` procedures anymore.
    procedure, public :: set => zmat_set
    procedure, public :: print => zmat_print
    procedure :: add => zmat_add_zmat
    generic, public :: operator(+) => add
  end type

contains

  function zmat_add_zmat(z1, z2) result(out)
    class(zmatrix(*)), intent(in) :: z1
    type(zmatrix(*)), intent(in) :: z2
    ! Match the len parameter of the output with the input.
    type(zmatrix(z1%rows)) :: out

    out%data = z1%data + z2%data
  end

  subroutine zmat_set(z, values)
    class(zmatrix(*)), intent(inout) :: z
    complex, intent(in) :: values
    z%data = values
  end

  subroutine zmat_print(z)
    class(zmatrix(*)), intent(in) :: z
    integer :: i

    ! You can inquiry the type parameter with a syntax similar to structure field.
    do i=1, z%rows
      write(*,*) z%data(i,:)
    end do
    write(*,*)
  end

end

Sample program using:

program main
  use zmatrix_module, only : zmatrix
  implicit None

  integer, parameter :: rows = 2

  ! A constant or initialization expression are allowed as the len parameter.
  type(zmatrix(rows)) :: mat1
  type(zmatrix(rows)) :: mat2
  ! If you declare an allocatable, you might declare the len parameter as deferred.
  type(zmatrix(:)), allocatable :: mat3(:)
    
  call mat1%set((10.0, 8.0))
  call mat2%set((1.0, 2.0))

  ! Note mat3 gets auto-allocated as a result of the operation.
  mat3 = mat1 + mat2

  ! You can also allocate the object explicitly, or using a source/mold.
  ! allocate(zmatrix(2) :: mat3)
  ! allocate(mat3, source=mat1)

  call mat3%print()
end
Related