Newer
Older
module CLA
! This si the Computational Linear Algebra module.
! It contains tools as routines to DO singular value decomposition and filtering
! and matrices inversion to compute coefficient for the monomial
! closure
USE prec_const
USE parallel
USE FMZM ! For factorial
implicit none
! local variables for DLRA and SVD
COMPLEX(xp), DIMENSION(:,:), ALLOCATABLE :: A_buff,Bf,Br ! buffer and full/reduced rebuilt matrices
COMPLEX(xp), DIMENSION(:,:), ALLOCATABLE :: U ! basis
REAL(xp), DIMENSION(:), ALLOCATABLE :: Sf, Sr ! full and reduced singular values
COMPLEX(xp), DIMENSION(:,:), ALLOCATABLE :: VT ! basis
INTEGER :: lda, ldu, ldvt, info, lwork, i, m,n, nsv_filter
COMPLEX(xp), DIMENSION(:), ALLOCATABLE :: work
REAL(xp), DIMENSION(:), ALLOCATABLE :: rwork
! local variables for monomial truncation (Hermite and Laguerre coeff)
! Coeff for the linear combination of the monomial truncation scheme
REAL(xp), PUBLIC, PROTECTED, DIMENSION(:), ALLOCATABLE :: c_Pp1, c_Pp2, c_Jp1
#ifdef TEST_SVD
! These routines are meant for testing SVD filtering
PUBLIC :: init_CLA, filter_sv_moments_ky_pj, test_SVD
#endif
PUBLIC :: set_monomial_trunc_coeff
CONTAINS
!--------------Routines to compute coeff for monomial truncation----------
SUBROUTINE set_monomial_trunc_coeff(Pmax,Jmax)
IMPLICIT NONE
INTEGER, INTENT(IN) :: Pmax,Jmax ! truncation degree (p,j>d are not evolved)
REAL(xp), DIMENSION(Pmax+3,Pmax+3) :: HH, iHH ! Hermite matrix to invert (+1 dim for p=0 and +2 dim for coeff P+1,P+2)
REAL(xp), DIMENSION(Jmax+2,Jmax+2) :: LL, iLL ! Laguerre matrix to invert (+1 dim for j=0 and +1 dim for coeff J+1)
INTEGER :: i,n
! Hermite
HH = 0._dp
DO i = 1,Pmax+3
n = i-1 !poly. degree
HH(1:i,i) = get_hermite_coeffs(n)
ENDDO
CALL inverse_triangular(Pmax+3,HH,iHH)
! Laguerre
LL = 0._dp
DO i = 1,Jmax+2
n = i-1 !poly. degree
LL(1:i,i) = get_laguerre_coeffs(n)
ENDDO
CALL inverse_triangular(Jmax+2,LL,iLL)
! Allocate and fill the monomial truncation coefficients
ALLOCATE(c_Pp1(Pmax+1))
DO i = 1,Pmax+1
c_Pp1(i) = -iHH(i,Pmax+2)/iHH(Pmax+2,Pmax+2)
ENDDO
ALLOCATE(c_Pp2(Pmax+1))
DO i = 1,Pmax+1
c_Pp2(i) = -iHH(i,Pmax+3)/iHH(Pmax+3,Pmax+3)
ENDDO
ALLOCATE(c_Jp1(Jmax+1))
DO i = 1,Jmax+1
c_Jp1(i) = 0*iLL(i,Jmax+2)/iLL(Jmax+2,Jmax+2)
ENDDO
END SUBROUTINE
! Invert an upper triangular matrix
SUBROUTINE inverse_triangular(N,U,invU)
IMPLICIT NONE
INTEGER, INTENT(in) :: N ! size of the matrix
REAL(xp), DIMENSION(N,N), INTENT(IN) :: U ! Matrix to invert
REAL(xp), DIMENSION(N,N), INTENT(OUT) :: invU! Result
! local variables
INTEGER :: info
invU = U
#ifdef LAPACKDIR
#ifdef SINGLE_PRECISION
CALL STRTRI('U','N',N,invU,N,info)
#else
CALL DTRTRI('U','N',N,invU,N,info)
#endif
#else
ERROR STOP "Cannot use monomial truncation without LAPACK"
#endif
IF (info .LT. 0) THEN
print*, info
ERROR STOP "STOP, LAPACK TRTRI: illegal value at index"
ELSEIF(info .GT. 0) THEN
ERROR STOP "STOP, LAPACK TRTRI: singular matrix"
ENDIF
END SUBROUTINE
! Compute the kth coefficient of the nth degree Hermite
! adapted from LaguerrePoly.m by David Terr, Raytheon, 5-10-04
FUNCTION get_hermite_coeffs(n) RESULT(hn)
IMPLICIT NONE
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
INTEGER, INTENT(IN) :: n ! Polyn. Deg. and index
REAL(xp), DIMENSION(n+1) :: hn ! Hermite coefficients
REAL(xp), DIMENSION(n+1) :: hnm2, hnm1, tmp
INTEGER :: k, e, factn
IF (n .EQ. 0) THEN
hn = 1._xp
ELSEIF (n .EQ. 1) THEN
hn = [2._xp, 0._xp]
ELSE
hnm2 = 0._xp
hnm2(n+1) = 1._xp
hnm1 = 0._xp
hnm1(n) = 2._xp
DO k = 2,n
hn = 0._xp
DO e = n-k+1,n,2
hn(e) = REAL(2*(hnm1(e+1) - (k-1)*hnm2(e)),xp)
END DO
hn(n+1) = REAL(-2*(k-1)*hnm2(n+1),xp)
IF (k .LT. n) THEN
hnm2 = hnm1
hnm1 = hn
END IF
END DO
END IF
! Normalize the polynomials
factn = 1
DO k = 1,n
factn = k*factn
ENDDO
hn = hn/SQRT(REAL(2**n*factn,xp))
! reverse order
tmp = hn
DO k = 1,n+1
hn(n+1-(k-1)) = tmp(k)
ENDDO
END FUNCTION
! Compute the kth coefficient of the nth degree Laguerre
! adapted from LaguerrePoly.m by David Terr, Raytheon, 5-11-04
! lnk = (-1)^k/k! binom(n,k), s.t. Ln(x) = sum_k lnk x^k
FUNCTION get_laguerre_coeffs(n) RESULT(ln)
IMPLICIT NONE
INTEGER, INTENT(IN) :: n ! Polyn. Deg. and index
REAL(xp), DIMENSION(n+1) :: ln ! Laguerre coefficients
REAL(xp), DIMENSION(n+1) :: lnm2, lnm1, tmp
INTEGER :: k, e
IF (n .EQ. 0) THEN
ln = 1._xp
ELSEIF (n .EQ. 1) THEN
ln = [-1._xp, 1._xp]
ELSE
lnm2 = 0._xp
lnm2(n+1) = 1._xp
lnm1 = 0._xp
lnm1(n) =-1._xp
lnm1(n+1) = 1._xp
DO k = 2, n
DO e = n-k+1, n
ln(e) = REAL((2*k-1)*lnm1(e) - lnm1(e+1) + (1-k)*lnm2(e),xp)
END DO
ln(n+1) = REAL((2*k-1)*lnm1(n+1) + (1-k)*lnm2(n+1),xp)
ln = ln/REAL(k,xp)
IF (k < n) THEN
lnm2 = lnm1
lnm1 = ln
END IF
END DO
END IF
! reverse order
tmp = ln
DO k = 1,n+1
ln(n+1-(k-1)) = tmp(k)
ENDDO
END FUNCTION
!--------------Routines to test singular value filtering -----------------
#ifdef TEST_SVD
SUBROUTINE init_CLA(m_,n_)
USE basic
IMPLICIT NONE
! ARGUMENTS
INTEGER, INTENT(IN) :: m_,n_ ! dimensions of the input array
! read the input
INTEGER :: lun = 90 ! File duplicated from STDIN
NAMELIST /CLA/ nsv_filter
READ(lun,CLA)
m = m_
n = n_
info = 1
! Allocate the matrices
ALLOCATE(A_buff(m,n),Bf(m,n),Br(m,n))
ALLOCATE(U(m,m),Sf(MIN(m,n)),Sr(MIN(m,n)),VT(n,n))
! Set the leading dimensions for the input and output arrays
lda = MAX(1, m)
ldu = MAX(1, m)
ldvt = MAX(1, n)
ALLOCATE(work(5*n), rwork(5*MIN(m,n)))
! Compute the optimal workspace size
lwork = -1
#ifdef SINGLE_PRECISION
CALL CGESVD('A', 'A', m, n, A_buff, lda, Sf, U, ldu, VT, ldvt, work, lwork, rwork, info)
#else
CALL ZGESVD('A', 'A', m, n, A_buff, lda, Sf, U, ldu, VT, ldvt, work, lwork, rwork, info)
! Allocate memory for the workspace arrays
lwork = 2*CEILING(REAL(work(1)))
DEALLOCATE(work)
ALLOCATE(work(lwork))
END SUBROUTINE init_CLA
SUBROUTINE filter_sv_moments_ky_pj
USE fields, ONLY: moments
USE grid, ONLY: total_nky, total_np, total_nj, ngp,ngj,ngz,&
local_np, local_nj, local_nz, local_nkx, local_nky, local_na
USE time_integration, ONLY: updatetlevel
USE basic, ONLY: start_chrono, stop_chrono, chrono_CLA
! Arguments
INTEGER :: nsv_filter ! number of singular values to keep
! Local variables
COMPLEX(xp), DIMENSION(:,:), ALLOCATABLE :: moments_lky_lpj ! local ky and local pj data
COMPLEX(xp), DIMENSION(:,:), ALLOCATABLE :: moments_gky_lpj ! global ky, local pj data
COMPLEX(xp), DIMENSION(:,:), ALLOCATABLE :: moments_gky_gpj ! full gathered data for SVD (input of SVD)
INTEGER :: ia,ix,iz, m,n, ip, ij, iy
CALL start_chrono(chrono_CLA)
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
ALLOCATE(moments_lky_lpj(local_nky,local_np*local_nj))
ALLOCATE(moments_gky_lpj(total_nky,local_np*local_nj))
ALLOCATE(moments_gky_gpj(total_nky,total_np*total_nj))
DO iz = 1+ngz/2,local_nz+ngz/2
DO ix = 1,local_nkx
DO ia = 1,local_na
! Build the local slice explicitely
DO ip = 1,local_np
DO ij = 1,local_nj
DO iy = 1,local_nky
moments_lky_lpj(iy,local_np*(ij-1)+ip) = moments(ia,ip+ngp/2,ij+ngj/2,iy,ix,iz,updatetlevel)
ENDDO
ENDDO
ENDDO
! Gather ky data
IF(num_procs_ky .GT. 1) THEN
! MPI communication
ELSE
moments_gky_lpj = moments_lky_lpj
ENDIF
! Gather p data
IF(num_procs_p .GT. 1) THEN
! MPI communication
ELSE
moments_gky_gpj = moments_gky_lpj
ENDIF
! The process 0 performs the SVD
IF(my_id .EQ. 0) THEN
m = total_nky
n = total_np*total_nj
nsv_filter = -1
CALL filter_singular_value(moments_gky_gpj)
ENDIF
! Distribute ky data
IF(num_procs_ky .GT. 1) THEN
! MPI communication
ELSE
moments_gky_lpj = moments_gky_gpj
ENDIF
! Distribute p data
IF(num_procs_p .GT. 1) THEN
! MPI communication
ELSE
moments_lky_lpj = moments_gky_lpj
ENDIF
! Put back the data into the moments array
DO ip = 1,local_np
DO ij = 1,local_nj
DO iy = 1,local_nky
moments(ia,ip+ngp/2,ij+ngj/2,iy,ix,iz,updatetlevel) = moments_lky_lpj(iy,local_np*(ij-1)+ip)
ENDDO
ENDDO
ENDDO
ENDDO
ENDDO
ENDDO
CALL stop_chrono(chrono_CLA)
END SUBROUTINE filter_sv_moments_ky_pj
SUBROUTINE filter_singular_value(A)
IMPLICIT NONE
! ARGUMENTS
COMPLEX(xp), DIMENSION(:,:), INTENT(INOUT) :: A ! Array to filter
! copy of the input since it is changed in the svd procedure
A_buff = A;
#ifdef SINGLE_PRECISION
CALL CGESVD('A', 'A', m, n, A_buff, lda, Sf, U, ldu, VT, ldvt, work, lwork, rwork, info)
#else
CALL ZGESVD('A', 'A', m, n, A_buff, lda, Sf, U, ldu, VT, ldvt, work, lwork, rwork, info)
IF(info.GT.0) print*,"SVD did not converge, info=",info
! Filter the values
Sr = 0._xp
IF (nsv_filter .GT. 0) THEN
DO i=1,MIN(m,n)-nsv_filter
Sr(i) = Sf(i)
ENDDO
ELSE ! DO not filter IF nsv_filter<0
Sr = Sf
ENDIF
! Reconstruct A from its reduced SVD
A = MATMUL(U, MATMUL(diagmat(Sr,m,n), VT)) ! reduced
END SUBROUTINE filter_singular_value
FUNCTION diagmat(S, m, n) RESULT(D)
REAL(xp), DIMENSION(:), INTENT(IN) :: S
COMPLEX(xp), DIMENSION(m,n) :: D
INTEGER :: i
! Initialize the output array to zero
D = 0.0
! Fill the diagonal elements of the output array with the input vector S
DO i = 1, MIN(m,n)
D(i,i) = S(i)
END FUNCTION diagmat
SUBROUTINE test_svd
! SpecIFy the dimensions of the input matrix A
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
INTEGER :: m,n
! Declare the input matrix A and reconstructed matrix B
COMPLEX(xp), DIMENSION(:,:), ALLOCATABLE :: A,B,A_buff
! OUTPUT
COMPLEX(xp), DIMENSION(:,:), ALLOCATABLE :: U
REAL(xp), DIMENSION(:), ALLOCATABLE :: S
COMPLEX(xp), DIMENSION(:,:), ALLOCATABLE :: VT
! local variables
INTEGER :: lda, ldu, ldvt, info, lwork, i, j
COMPLEX(xp), DIMENSION(:), ALLOCATABLE :: work
REAL(xp), DIMENSION(:), ALLOCATABLE :: rwork
m = 3
n = 2
ALLOCATE(A_buff(m,n),A(m,n),B(m,n),U(m,m),S(MIN(m,n)),VT(n,n))
! Set the leading dimensions for the input and output arrays
lda = MAX(1, m)
ldu = MAX(1, m)
ldvt = MAX(1, n)
! Define the input matrix A
A = RESHAPE((/ (1._xp,0.1_xp), (2._xp,0.2_xp), (3._xp,0.3_xp), (4._xp,0.4_xp), (5._xp,0.5_xp), (6._xp,0.6_xp) /), SHAPE(A))
! copy of the input since it is changed in the svd procedure
A_buff = A;
! Print the input matrix A
WRITE(*,*) 'Input matrix A = '
DO i = 1, m
WRITE(*,*) ('(',REAL(A(i,j)), AIMAG(A(i,j)),')', j=1,n)
END DO
! CALL svd(A, U, S, VT, m, n)
ALLOCATE(work(5*n), rwork(5*n))
! Compute the optimal workspace size
lwork = -1
#ifdef SINGLE_PRECISION
CALL CGESVD('A', 'A', m, n, A_buff, lda, S, U, ldu, VT, ldvt, work, lwork, rwork, info)
#else
CALL ZGESVD('A', 'A', m, n, A_buff, lda, S, U, ldu, VT, ldvt, work, lwork, rwork, info)
#endif
! Allocate memory for the workspace arrays
lwork = CEILING(REAL(work(1)), KIND=SELECTED_REAL_KIND(1, 6))
! Compute the SVD of A using the LAPACK subroutine CGESVD
#ifdef SINGLE_PRECISION
CALL CGESVD('A', 'A', m, n, A_buff, lda, S, U, ldu, VT, ldvt, work, lwork, rwork, info)
#else
CALL ZGESVD('A', 'A', m, n, A_buff, lda, S, U, ldu, VT, ldvt, work, lwork, rwork, info)
#endif
! Print the results
! WRITE(*,*) 'U = '
! DO i = 1, m
! WRITE(*,*) ('(',REAL(U(i,j)), AIMAG(U(i,j)),')', j=1,m)
! END DO
! WRITE(*,*)
WRITE(*,*) 'S = '
WRITE(*,'(2F8.3)') (S(i), i=1,n)
WRITE(*,*)
! WRITE(*,*) 'VT = '
! DO i = 1, n
! WRITE(*,*) ('(',REAL(VT(i,j)), AIMAG(VT(i,j)),')', j=1,n)
! END DO
! Reconstruct A from its SVD
B = MATMUL(U, MATMUL(diagmat(S,m,n), VT))
! Print the error with the intput matrix
WRITE(*,*) '||A-USVT||=', sum(abs(A-B))
stop
END SUBROUTINE test_svd
#endif
END module CLA