LAPACK routine ZGEEV - gives wrong eigenvalues - fortran90

I wrote the following program in fortran that uses a lapack subroutine called ZGEEV. The idea was to see how the eigenvalues of the matrix change as k goes from real to complex. Analytically, the answers should be 2 and 0, whether k is complex or not. But I obtain a plot that shows a lot variation.
Especially for real k, the plot looks like this -
Here is the code i wrote -
program main
implicit none
!**********************************************
complex(8) :: k,mat(2,2)
complex(8) :: eigenvals(2)
real(8), parameter :: kmax = 2.d0
real(8), parameter :: dk = 1.d-1
real(8) :: kr,ki
!**********************************************
kr=-kmax
do while (kr.le.kmax)
ki= -1.d-3
do while (ki.le.1.d-3)
k=cmplx(kr,ki)
call init_mat(k,mat)
call diagonalize(mat,eigenvals)
print*, real(k), real(eigenvals(2)),aimag(eigenvals(2))
ki=ki+1.d-4
end do
kr=kr+dk
end do
end program main
subroutine init_mat(k,mat)
implicit none
complex(8),intent(in) :: k
complex(8),intent(out):: mat(2,2)
complex(8),parameter :: di=(0.d0,1.d0)
complex(8),parameter :: d1=(1.d0,0.d0)
!**********************************************
mat(1,1) = d1
mat(1,2) = exp(di*k)
mat(2,1) = exp(di*k)
mat(2,2) = d1
return
end subroutine init_mat
subroutine diagonalize(mat,eigenvals)
implicit none
complex(8),intent(in) :: mat(2,2)
complex(8),intent(out):: eigenvals(2)
complex(8) :: vl(2,2),vr(2,2)
complex(8),allocatable:: work(:)
integer(4) :: lwork
complex(8) :: rwork(4)
complex(8) :: mat2(2,2)
integer(4) :: info
!**********************************************
mat2(:,:) = mat(:,:)
allocate(work(6))
call zgeev('N', 'N', 2, mat2, 2, eigenvals, vl, 2, vr, 2, work, -1, rwork, info)
lwork = work(1)
deallocate(work)
allocate(work(lwork))
call zgeev('V', 'V', 2, mat2, 2, eigenvals, vl, 2, vr, 2, work, lwork, rwork, info)
if (info.ne.0) print*, info
stop 'diagonalize failed'
end subroutine diagonalize
Any lazy theorizing as to the causes of this aberration is welcome in the comments!
PS: i wrote up a similar code in python and there the eigenvalues are two constant lines at y=2 and y=0.

in subroutine init_mat(k,mat)
mat(1,2) = exp(di*k)
and
mat(2,1) = exp(di*k)
But one of them, e.g., mat(2,1) should = exp(-di*k)
Although your math project calls for a matrix with e^ik and e^-ik on the off-diagonals, the code shown instead is creating a matrix with e^ik on both off-diagonals. The matrix actually coded has complex eigenvalues, so the subroutines for finding eigenvalues may be working correctly and the input as shown has a mis-specification.
So what are the eigenvalues of [[1, e^ik], [e^ik, 1]]?
Well, the trace is still 2, so the eigenvalues sum to 2.
And the determinant is 1-e^(2ik), so the product is complex.
This suggests that the eigenvalues of the matrix actually input are complex conjugates that sum to 2. By inspection, the eigenvalues seem to be 1 +/- e^ik

Related

Portable declaration of REAL variables in mex gateway for Fortran

I am writing a mex gateway for a piece of Fortran code.
In the Fortran code, for portability, the floating-point variables are declared as
REAL(kind(0.0D0)) :: x, y, etc
(BTW, I am aware that there are better ways to do it, as discussed at
Fortran: integer*4 vs integer(4) vs integer(kind=4),
What does "real*8" mean?, and
https://software.intel.com/en-us/blogs/2017/03/27/doctor-fortran-in-it-takes-all-kinds )
However, it seems to me that mex supports only REAL*8 and REAL*4, the former being Double, the latter being Single. I got this impression from the following functions/subroutines:
mxIsDouble, mxIsSingle, mxCopyPtrToReal8, mxCopyReal8ToPtr, mxCopyPtrToReal4, mxCopyReal4ToPtr
My questions are as follows.
Is it true that mex supports only REAL*8 and REAL*4?
Does it improve the portability of the mex gateway if I declare double-precision floating-point variables as
REAL(kind(0.0D0)) :: x, y, etc
or even
integer, parameter :: dp = selected_real_kind(15, 307)
real(kind=dp) :: x, y, etc
Or should I simply declare
REAL*8 :: x, y, etc
Are REAL*8 and/or REAL*4 supported on all platforms? If no, does this mean that MATLAB mex is intrinsically unportable?
What is the best way to specify the kind of floating-point variables in mex gateways for Fortran code?
The following code is an example. See the declaration of x, y, and xs.
#include "fintrf.h"
subroutine mexFunction(nlhs, plhs, nrhs, prhs)
C y = square (x)
C x: a floating point scalar
C y: x^2
implicit none
C mexFunction arguments
integer, intent(in) :: nlhs, nrhs
mwPointer, intent(in) :: prhs(nrhs)
mwPointer, intent(inout) :: plhs(nlhs)
C function declarations:
mwPointer, external :: mxCreateDoubleScalar, mxGetPr
mwSize, external :: mxGetM, mxGetN
integer*4, external :: mxIsDouble, mxIsSingle
C variables
mwSize, parameter :: mwOne = 1
integer, parameter :: dKind = kind(0.0D0)
integer, parameter :: sKind = kind(0.0)
real(kind=dKind) :: x, y ! Does this improve the portablity?
real(kind=sKind) :: xs ! Does this improve the portablity?
C validate number of arguments
if (nrhs .ne. 1) then
call mexErrMsgIdAndTxt ('mex:nInput', '1 input required.')
endif
if (nlhs .gt. 1) then
call mexErrMsgIdAndTxt ('mex:nOutput', 'At most 1 output.')
endif
C validate input
if (mxIsDouble(prhs(1)) .ne. 1 .and. mxIsSingle(prhs(1)) .ne. 1)
! What if the input is a floating point number but neither Double nor Single?
+ then
call mexErrMsgIdAndTxt ('mex:Input', 'Input a real number.')
endif
if (mxGetM(prhs(1)) .ne. 1 .or. mxGetN(prhs(1)) .ne. 1) then
call mexErrMsgIdAndTxt ('mex:Input', 'Input a scalar.')
endif
C read input
if (mxIsDouble(prhs(1)) .eq. 1) then
call mxCopyPtrToReal8(mxGetPr(prhs(1)), x, mwOne)
else
call mxCopyPtrToReal4(mxGetPr(prhs(1)), xs, mwOne)
x = real(xs, dKind)
! What if the input is a floating point number but neither REAL*8 nor REAL*4
endif
C do the calculation
y = x**2
C write output
plhs(1) = mxCreateDoubleScalar(y)
return
end subroutine mexFunction
The code runs correctly. Yet I am not sure whether it is portable.
REAL*4 and REAL*8 are non-standard and non-portable. REAL(KIND(0.0D0) gets you DOUBLE PRECISION on every platform, as this is required by the Fortran standard.
I can't speak to MEX gateways, but you should avoid obvious non-standard features.
A popular choice is to define a module that declares named (PARAMETER) constants for the kinds in use. For example:
module kinds
integer, parameter :: SP = KIND(0.0)
integer, parameter :: DP = KIND(0.0D0)
end module kinds
Then you can use SP and DP as kind values. If you ever need to change these, just edit the module.
Currently, it makes no difference whether you define variables as REAL*8/REAL*4 or REAL(REAL64)/REAL(REAL32). In the future MathWorks may come around and rewrite their functions to use portable variable declarations, but in my opinion this is unlikely for many reasons.
If you look in the fintrf.h file (included in every Fortran MEX gateway source file), you'll see that all of the MEX-specific procedures are defined with "asterisk notation," e.g. # define MWPOINTER INTEGER*8. So even if you define all of your variables with kinds from iso_fortran_env or selected_real_kind, any time you use a MathWorks variable type you're still using "asterisk notation" types, unless you go through that header file and redefine every symbol using your chosen kind specification.

Matlab from Fortran - problems transferring big matrix

I have to call Matlab from Fortran and execute a program there. I have a large 3xN (N is around 2500) matrix of data, which needs to be transferred to Matlab. I noticed some discrepancies in the data - the last line of the Fortran matrix becomes first line in Matlab (other lines stay however on their place, shifted down by 1), and this line also looses the first value.
Like this - In Fortran
1.1 1.2 1.3
2.1 2.2 2.3
.....
1999.1 1999.2 1999.3
2000.1 2000.2 2000.3
becomes in Matlab
0.0 2000.2 2000.3
1.1 1.2 1.3
2.1 2.2 2.3
.....
1999.1 1999.2 1999.3
I cant understand what is going wrong somehow.. Spent several hours...
node_xyz_ini = mxCreateDoubleMatrix(M, N, 0) ! M, N - dimensions
call mxCopyReal8ToPtr(CoordSet, mxGetPr(node_xyz_ini), M*N)
I use Octave rather than matlab. With that as a caveat, here is an example of what I use, this for double precision twod arrays:
MODULE IO
use, intrinsic :: iso_c_binding
!! use c_float,c_double, c_double_complex, c_int,c_ptr
implicit none
real (c_double), allocatable :: x(:,:),h(:),f(:)
integer (c_int),allocatable :: t(:,:)
integer (c_int) :: nx,ne
contains
Subroutine Write_Array_RDP(varname,variable)
implicit none
integer (c_int) :: kx,ky,sh(2),ncol,nrow
character(len=7),intent(in) :: varname
character(:),allocatable :: wrtfmt
character(range(ncol)) :: res
real(c_double),intent(in) :: variable(:,:)
open(unit=10,file=varname,form="formatted",status="replace",action="write")
write(10,fmt="(A)")"# created by ?? "
sh=shape(variable)
ncol=sh(2);nrow=sh(1)
write(10,fmt="(A,A)")"# name: ",varname
write(10,fmt="(A)")"# type: matrix"
write(10,fmt="(A,i0)")"# rows: ",nrow
write(10,fmt="(A,i0)")"# columns: ",ncol
write(res,'(i0)') ncol
wrtfmt="("//trim(res)//"(e20.12))"
do ky=1,nrow
write(10,fmt=wrtfmt)(variable(ky,kx),kx=1,ncol)
end do
write(10,*)" "
write(10,*)" "
close(10)
End Subroutine Write_Array_RDP
END MODULE IO
Program Main
use IO
implicit none
real (c_double),allocatable :: DPArray(:,:)
allocate(DPArray(3,3))
DPArray=reshape((/1.0d0,2.0d0,3.0d0,1.0d0,2.0d0,3.0d0,1.0d0,2.0d0,3.0d0/),(/3,3/))
Call Write_Array_RDP('DPArray',DPArray)
End Program Main
I compile and link with 'gfortran name.f90' then run with ./a.out. The file DPArray has been created. Then in Octave :
load DPArray
DPArray
produces the output:
1 1 1
2 2 2
3 3 3
I have found it necessary to recode the Write subroutine for different variable types (Write_Array_CMPLX, Write_Array_INT) etc...

Calculating a checksum of a real array in Fortran

I have a large array in Fortran:
real, dimension(N) :: arr
And I need to check if the array is exactly the same in different runtimes of the program. To do this, I wanted to create a checksum of the array to compare. However, I don't know which algorithm to implement. I have looked at Flether's and Adler's algorithm, but have trouble reading the C syntax provided in the examples I found. And also, I don't know how to implement them with Reals instead of chars/integers.
In the C implementations I have found they return:
return (b << 16) | a;
But I don't know how to implement the b << 16 part in Fortran, or if this translates well to reals.
I finally solved the issue by implementing Adler-32 in Fortran:
subroutine test_hash(var)
implicit none
real, dimension(N), intent(in) :: var
integer, dimension(N) :: int_var
integer :: a=1, b=0, i=1, mod_adler=65521, hash = 0
int_var = TRANSFER(var, a, nijk)
do i= 1, NIJK
a = MOD(a + int_var(i), mod_adler)
b = MOD(b+a, mod_adler)
end do
hash = ior(b * 65536, a)
print*, hash
end subroutine test_hash
I ended up using the Fortran intrinsic Transfer function to convert the 32bit reals to 32bit integers, since that's what the algorithm relies on. After this I perform the standard loop. Use the IOR function as suggested by #VladimirF and represented the b<<16 as b * 65536 described by #ja72.
Finally I'll be able to print the hash to the console.
The reason for implementing it this way was because it's faster in use than opening a file, computing the checksum per file. The main reason for this is because there are many variables I need to check which switch often since I'm only using this for debugging purposes.
A modified version of Lars accomplishes the same without a large temporary array. Also, in Fortran, initializing the variable at declaration time implies the "save" attribute, which is not desirable in this case.
function hash_real_asz(var,size_var) result(hash)
implicit none
integer(8) :: hash
real(8), dimension(*), intent(in) :: var
integer, intent(in) :: size_var
integer(4) :: a,b,i,j
integer(4), parameter :: mod_adler = 65521
integer(4), allocatable :: tmp(:)
a = 1
b = 0
do i= 1, size_var
tmp = transfer(var(i), [0]) ! tmp will be an integer array sufficient to hold var(i)
do j = 1,size(tmp)
a = MOD(a+tmp(j), mod_adler)
b = MOD(b+a, mod_adler)
end do
end do
hash = ior(b * 65536, a)
end function

How to read binary file written by Matlab in Fortran?

I want to read an array of double precision values written in a binary file by Matlab into a code in Fortran (compiler gfortran), however my code to read it is not working. Can you please show me the correct way to do it?
Here is my Matlab code, which works.
a=[0.6557 0.0357 0.8491 0.9340 0.6787];
fid=fopen('ft1.bin','w');
fwrite(fid,a,'double');
fclose('all');
fid=fopen('ft1.bin','r');
a2=fread(fid,5,'double');
fclose('all');
a2
Here is my Fortran, code which returns an error when I try to read file ft1.bin
program code1
implicit none
double precision, dimension(5) :: a2
integer :: i
open(1,FILE="ft1.bin",FORM='UNFORMATTED',ACTION='READ')
read(1) a2
close(1)
print *, a2
end program code1
When I try to run it,
gfortran code1.f90 -o bb1
./bb1
At line 8 of file code1.f90 (unit = 1, file = 'ft1.bin')
Fortran runtime error: Unformatted file structure has been corrupted
One has to avoid the record based I/O with ACCESS="STREAM", e.g.,
PROGRAM test
IMPLICIT NONE
INTEGER, PARAMETER :: dp = KIND(1D0)
INTEGER :: funit, io_stat
REAL(dp) :: a(5)
OPEN(NEWUNIT = funit, FILE = 'ft1.bin', STATUS = "OLD", ACCESS = "STREAM", FORM = "UNFORMATTED", IOSTAT = io_stat)
READ(funit, IOSTAT = io_stat) a
WRITE(*, *) a
CLOSE(funit)
END PROGRAM

FORTRAN 90 separating digits in an integer

Hej folks, I'm quite the beginner in programming but I read my share of stackoverflow pages, and googled a bit as well, still can't figure if the following is even possible in FORTRAN 90.
I'm trying to isolate the digits in an integer, to point where the hurdle is, consider the following idea :
INTEGER :: n, mult, add
READ *, n ! n = 8
mult = n*2 ! = 16
add = ??? ! where I want to add 1 + 6
Another way, I trust that this will be obvious to anyone reading the code:
INTEGER FUNCTION sum_digits(num)
INTEGER, INTENT(in) :: num
INTEGER, DIMENSION(:), ALLOCATABLE :: digs
INTEGER :: num_digits, ix, rem
num_digits = FLOOR(LOG10(REAL(num))+1)
ALLOCATE(digs(num_digits))
rem = num
DO ix = 1, num_digits
digs(ix) = rem - (rem/10)*10 ! Take advantage of integer division
rem = rem/10
END DO
sum_digits = SUM(digs)
END FUNCTION sum_digits
I've subjected this to a quick series of obvious tests and it has passed all 4 of them. If you find a case for which it doesn't work, fix it. And if you want the array of digits returned, modify the function to return that. If you want it to work for negative integers too throw in ABS() at an appropriate place.
one way to pull off the 'ith' place digit is:
n/10**i-10*(n/10**(i+1))
so for your example:
n-10*(n/10) + n/10-10*(n/100)