Fortran Matrix Division - matrix

I am trying to divide matrices in Fortran(f90). X=R/Z =>
X[6x1], R[6x6] and Z[6x1] are matrices.
Normally in Matlab, You can write that equation and easy but in Fortran little bit different because Fortran has no any division properties so that I need to take the inverse of Z matrix (X=R*Z^-1).
The problem is matrix shape because here Z matrix(6x1) and not square so I cant take the inverse of that. Is there any ready function for the division and How can handle if the inverse of the matrix is not square?
program main
implicit none
real, dimension(6,1) :: X
real, dimension(6,6) :: R
real, dimension(6,1) :: Z
real, dimension(6,1) :: Z_inv
!X=R/Z
call mat_inverse(Z) ! Please ignore inverse part
Z_inv=Z ! Mat_inverse function is already defined in math
! Kernel Library but I just call that mat_inverse
! Because I call mat_inverse subroutine
! X=R*Z^-1
X=Matmul(R,Z_inv)
end program main

Related

Logarithm of a random number

I wrote a fortran program to code this algorithm (https://en.wikipedia.org/wiki/Reservoir_sampling#Algorithm_A-ExpJ). It works on my computer. But after I asked these two questions (Intrinsic Rand, what is the interval [0,1] or ]0,1] or [0,1[ and How far can we trust calculus with infinity?), I think I could have a problem with log(random()) because call random_number(Xw); Xw = log(Xw) is used.
Indeed, random_number(Xw) could return 0 and log(0)=-infinity.
Therefore, I plan to modify this line as follow call random_number(Xw); Xw = log(1-Xw) to change the random value interval from [0,1[ to ]0,1].
Is it a good idea or is there a best solution ?
While mathematically it is true that if X is uniformly distributed on (the real interval) [0,1) then 1-X is uniformly distributed on (0,1], this does not particularly help you.
As noted in the description of the algorithm to which you link, the underlying assumption is that the base uniform distribution is over the interval (0,1). This is not the same as (0,1].
You can use rejection sampling to generate X uniformly over (0,1) from random_number() (which is [0,1)): throw away all zero occurrences.
Not a good idea. If you want your algorithm to be stable, you need to define bounds. your log function represents a priority, it can likely be as low as you want, but it must be a number. You can bind it to numerical precision:
program t
use iso_fortran_env
implicit none
real(real64), parameter :: SAFE = exp(-0.5d0*huge(0.0_real64))
print *, log(randoms_in_range(100,SAFE,1.0_real64))
contains
elemental real(real64) function in_range(f,low,hi) result(x)
real(real64), intent(in) :: f ! in: [0:1]
real(real64), intent(in) :: low,hi
real(real64) :: frac
frac = max(min(f,1.0_real64),0.0_real64)
x = low+frac*(hi-low)
end function in_range
real(real64) function random_in_range(low,hi) result(x)
real(real64), intent(in) :: low,hi
call random_number(x) ! [0,1]
x = in_range(x,low,hi) ! [low,hi]
end function random_in_range
function randoms_in_range(n,low,hi) result(x)
integer , intent(in) :: n
real(real64), intent(in) :: low,hi
real(real64) :: x(n)
call random_number(x) ! [0,1]
x = in_range(x,low,hi) ! [low,hi]
end function randoms_in_range
end program

LAPACK Eigenvectors Sign Inverted for Similar Matrices

I am using LAPACK to perform some eigendecomposition on two relatively small matrices (15x15, in this case). These matrices are from an optimisation problem, but their origin is not of great importance. They are both quite similar as it is a small step in an optimisation algorithm, so I would expect their eigenvectors and eigenvalues to be of similar magnitude, and certainly the same sign. However, after obtaining the eigenvectors of both matrices using LAPACK's DSYEVD (I have also tried DSYEV with the same result), I find that some eigenvectors show expected similarity, but others have total inversion of their signs which is rather strange. WeTransfer link for these matrices: https://wetransfer.com/downloads/784e73883eae7c63aa06aa70b64d462b20220831121100/b7519a.
I am using a module for maths that contains two functions, one for eigenvalues and one for eigenvectors.
MODULE math
implicit none
contains
function EVALS(matrix, n) result(eigenvals)
! Here, the eigenvalues from a square, symmetric, real matrix are calculated.
! The eigenvectors associated with said eigenvalues are not given as a result, but can be obtained with the function EVEVS.
!
! ARGUMENTS: matrix : 2D array containing the matrix which the eigenvalues of will be calculated.
! n : integer which represents the number of rows/columns (it doesn't matter which as the matrix is square).
implicit none
integer(i4b), intent(in) :: n
integer(i4b) :: LDA, LWORK, LIWORK, INFO
real(dp), intent(in) :: matrix(n, n)
real(dp), allocatable :: WORK(:), IWORK(:)
real(dp) :: eigenvals(n), work_mat(n,n)
character :: JOBZ, UPLO
! Assigning working matrix...
work_mat(:,:) = 0.0
work_mat(:,:) = matrix(:,:)
! Initialising some values....
JOBZ = 'N'
UPLO = 'U'
LDA = n
INFO = 0
! Allocating working array...
LWORK = MAX(1, (1 + 6*n + 2*n**2))
LIWORK = MAX(1, (3 + 5*n))
allocate(WORK(LWORK))
allocate(IWORK(LIWORK))
WORK(:) = 0
IWORK(:) = 0
! Obtaining eigenvalues...
eigenvals(:) = 0.0
call DSYEVD(JOBZ, UPLO, n, work_mat, LDA, eigenvals, WORK, LWORK, IWORK, LIWORK, INFO)
deallocate(WORK)
end function EVALS
function EVECS(matrix, n) result(eigenvecs)
! Here, the eigenvectors of a square, symmetric, real matrix are calculated.
! The eigenvalues associated with said eigenvectors are not given as a result, but can be obtained with the function EVALS.
!
! ARGUMENTS: matrix : 2D array containing the matrix which the eigenvalues of will be calculated.
! n : integer which represents the number of rows/columns (it doesn't matter which as the matrix is square).
implicit none
integer(i4b), intent(in) :: n
integer(i4b) :: LDA, LWORK, LIWORK, INFO
real(dp), intent(in) :: matrix(n, n)
real(dp), allocatable :: WORK(:), IWORK(:)
real(dp) :: eigenvecs(n, n), eigenvals(n), work_mat(n,n)
character :: JOBZ, UPLO
! Assigning working matrix...
work_mat(:,:) = 0.0
work_mat(:,:) = matrix(:,:)
! Initialising some values....
JOBZ = 'V'
UPLO = 'U'
LDA = n
INFO = 0
! Allocating working array....
LWORK = MAX(1, (1 + 6*n + 2*n**2))
LIWORK = MAX(1, (3 + 5*n))
allocate(WORK(LWORK))
allocate(IWORK(LIWORK))
WORK(:) = 0
IWORK(:) = 0
! Obtaining eigenvectors...
eigenvals(:) = 0.0
call DSYEVD(JOBZ, UPLO, n, work_mat, LDA, eigenvals, WORK, LWORK, IWORK, LIWORK, INFO)
eigenvecs(:,:) = 0.0
eigenvecs(:,:) = work_mat(:,:)
deallocate(WORK)
end function EVECS
END MODULE math
Now, below is an minimal example of how these functions are used within another source code file. I have printed two eigenvectors (one from each matrix) which are very similar in their values, but the signs are opposite. If you were to look at other corresponding eigenvectors, some are more or less similar (as expected), and others show this same opposite signs behaviour.
program eigen_test
use math
implicit none
integer(i4b) :: i, j
integer(i4b), parameter :: npr=15
real(dp) :: mat_a(npr,npr), mat_b(npr,npr)
real(dp) :: eigenvals_a(npr), eigenvals_b(npr)
real(dp) :: eigenvecs_a(npr,npr), eigenvecs_b(npr,npr)
open(10, file="mat_a")
read(10,*) ((mat_a(i,j), j=1,npr), i=1,npr)
open(11, file="mat_b")
read(11,*) ((mat_b(i,j), j=1,npr), i=1,npr)
eigenvals_a = EVALS(mat_a, npr)
eigenvecs_a = EVECS(mat_a, npr)
eigenvals_b = EVALS(mat_b, npr)
eigenvecs_b = EVECS(mat_b, npr)
print *, eigenvecs_a(:,4)
print *, eigenvecs_b(:,4)
end program eigen_test
Perhaps this is a problem with my eigendecomposition understanding, or maybe I am not using the LAPACK function correctly, but I hope the problem is clear and reproducible.
Thanks in advance!

How to properly call the SGEMV in Fortran?

I want to perform a Matrix-Vector product in fortran using the SGEMV subroutine from BLAS.
I have a code that is similar to this:
program test
integer, parameter :: DP = selected_real_kind(15)
real(kind=DP), dimension (3,3) :: A
real(kind=DP), dimension (3) :: XP,YP
call sgemv(A,XP,YP)
A is a 3x3 Matrix, XP and YP are Vectors.
In the included module one can see the following code:
PURE SUBROUTINE SGEMV_F95(A,X,Y,ALPHA,BETA,TRANS)
! Fortran77 call:
! SGEMV(TRANS,M,N,ALPHA,A,LDA,X,INCX,BETA,Y,INCY)
USE F95_PRECISION, ONLY: WP => SP
REAL(WP), INTENT(IN), OPTIONAL :: ALPHA
REAL(WP), INTENT(IN), OPTIONAL :: BETA
CHARACTER(LEN=1), INTENT(IN), OPTIONAL :: TRANS
REAL(WP), INTENT(IN) :: A(:,:)
REAL(WP), INTENT(IN) :: X(:)
REAL(WP), INTENT(INOUT) :: Y(:)
END SUBROUTINE SGEMV_F95
I understand that the some of the parameters are optional, so where am i wrong in the method call?
When you look at BLAS or LAPACK routines then you should always have a look at the first letter:
S: single precision
D: double precision
C: single precision complex
Z: double precision complex
You defined your matrix A as well as the vectors XP and YP as a double precision number using the statement:
integer, parameter :: DP = selected_real_kind(15)
So for this, you need to use dgemv or define your precision as single precision.
There is also a difference between calling dgemv and dgemv_f95. dgemv_f95 is part of Intel MKL and not really a common naming. For portability reasons, I would not use that notation but stick to the classic dgemv which is also part of Intel MKL.
DGEMV performs one of the matrix-vector operations
y := alpha*A*x + beta*y, or y := alpha*A**T*x + beta*y,
where alpha and beta are scalars, x and y are vectors and A is an
m by n matrix.
If you want to know how to call the function, I suggest to have a look here, but it should, in the end, look something like this:
call DGEMV('N',3,3,ALPHA,A,3,XP,1,BETA,YP,1)
The precisions are incompatible. You are calling sgemv which takes single precision arguments but you are passing double precision arrays and vectors.
Perhaps the trans parameter is required?
trans: Must be 'N', 'C', or 'T'.
(As per the note at the bottom of Developer Reference for IntelĀ® Math Kernel Library - Fortran.)

QR decomposition Fortran error

I have a problem with QR decomposition method. I use dgeqrf subroutine for decomposition but there is no error in the compiler but it gives a problem after that. I haven't found where is the mistake.
Another question is, A=Q*R=> if A matrix has zero, Can decomposition be zero or lose the rank.
program decomposition
!CONTAINS
!subroutine Qrdecomposition(A_mat, R)
real,dimension(2,2) :: A_mat !real,dimension(2,2),intent(inout)
:: A_mat
real,dimension(2,2) :: R !real,dimension(2,2),intent(out)
:: R
real,dimension(2,2) :: A
integer :: M,N,LDA,LWORK,INFO
real,allocatable, dimension(:,:) :: TAU
real,allocatable, dimension(:,:) :: WORK
external dgeqrf
M=2
N=2
LDA=2
LWORK=2
INFO=0
A_mat(1,1)=4
A_mat(1,2)=1
A_mat(2,1)=3
A_mat(2,2)=1
A=A_mat
call dgeqrf(M,N,A,TAU,WORK,LWORK,INFO)
R=A
print *,R,WORK,LWORK
!end subroutine Qrdecomposition
end program decomposition
I see three mistakes in your code:
1) You forgot the LDA argument to dgeqrf,
2) TAU and WORK must be explicitly allocated,
3) All arrays should be declared with double precision to be consistent with dgeqrf interface:
program decomposition
!CONTAINS
!subroutine Qrdecomposition(A_mat, R)
! Note: using '8' for the kind parameter is not the best style but I'm doing it here for brevity.
real(8),dimension(2,2) :: A_mat !real,dimension(2,2),intent(inout)
real(8),dimension(2,2) :: R !real,dimension(2,2),intent(out)
real(8),dimension(2,2) :: A
integer :: M,N,LDA,LWORK,INFO
real(8),allocatable, dimension(:,:) :: TAU
real(8),allocatable, dimension(:,:) :: WORK
external dgeqrf
M=2
N=2
LDA=2
LWORK=2
INFO=0
A_mat(1,1)=4
A_mat(1,2)=1
A_mat(2,1)=3
A_mat(2,2)=1
A=A_mat
allocate(tau(M,N), work(M,N))
call dgeqrf(M,N,A,LDA,TAU,WORK,LWORK,INFO)
R=A
print *,R,WORK,LWORK
!end subroutine Qrdecomposition
end program decomposition
In certain situations, Fortran does perform automatic allocation of arrays, but it should generally not be counted on and it is not the case here.
EDIT Point 3 was pointed out by roygvib, see below.

Constant shape error in simple code

I have an error with this code and I don't understand why
-"The module or main program array 'u' at (1) must have constant shape."
-Moreover, how can I do this code with a choice of parameters, I mean [U]=vector(N) where I can chose N and it returns me U.
program vector
!declaration
implicit none
integer :: n
integer, parameter :: N=10
real, dimension(N,1) :: U
do n=1,N
U(1,N)=n
end do
print*,U
end program vector
First up, Fortran is caseINsensitive, so n and N are the same thing, and you can't declare two different variables/parameters n and N.
Then you declare U to have shape (N, 1), but seem to use it in the form (1, N).
As for how to auto-generate something like U, you could use something like this:
function vector(n) result(v)
integer, intent(in) :: n
integer :: v(n)
integer :: i
v = [ (i, i=1, n) ]
return
end function vector
One more thing:
You declare U with dimension(1, N) which creates a 2D array with one dimension having length 1. I'm wondering whether you wanted to create a 1D array with range from 1 to N, for which the declaration would need to be dimension(1:N) (or, since Fortran assumes indices start at 1, just dimension(N)).
Addressing the questions in your comment:
The purpose of intent(in) tells the compiler that n is only read, not written to, in this function. Considering that you want to use n as the size of array v, you want that.
With result(v) I tell the compiler that I want to use the name v to refer to the result of the function, not the default (which is the function name). I do this to avoid confusion.
integer :: v(n) is the same as integer, dimension(n) :: v

Resources