diff options
| author | stainer_t <thomas.stainer@oecd-nea.org> | 2025-09-08 13:48:49 +0200 |
|---|---|---|
| committer | stainer_t <thomas.stainer@oecd-nea.org> | 2025-09-08 13:48:49 +0200 |
| commit | 7dfcc480ba1e19bd3232349fc733caef94034292 (patch) | |
| tree | 03ee104eb8846d5cc1a981d267687a729185d3f3 /Utilib/src/ALHQR.f90 | |
Initial commit from Polytechnique Montreal
Diffstat (limited to 'Utilib/src/ALHQR.f90')
| -rw-r--r-- | Utilib/src/ALHQR.f90 | 283 |
1 files changed, 283 insertions, 0 deletions
diff --git a/Utilib/src/ALHQR.f90 b/Utilib/src/ALHQR.f90 new file mode 100644 index 0000000..35ac064 --- /dev/null +++ b/Utilib/src/ALHQR.f90 @@ -0,0 +1,283 @@ +! +!----------------------------------------------------------------------- +! +!Purpose: +! find the eigenvalues and corresponding eigenvectors of equation +! (a-eval)*evect=0 using the power method with the shifted Hessenberg +! QR algorithm. +! +!Copyright: +! Copyright (C) 2020 Ecole Polytechnique de Montreal +! This library is free software; you can redistribute it and/or +! modify it under the terms of the GNU Lesser General Public +! License as published by the Free Software Foundation; either +! version 2.1 of the License, or (at your option) any later version. +! +!Author(s): A. Hebert +! +!Reference: +! G. E. Robles, "Implementing the QR algorithm for efficiently +! computing matrix eigenvalues and eigenvectors," Final Degree +! Dissertation in Mathematics, Universidad del Pais Vasco, Spain +! (2017). +! +!Parameters: input +! ndim dimensioned column length of A. +! n order of matrix A. +! A input matrix. +! maxiter maximum number of iterations. +! +!Parameters: output +! iter actual number of iterations. +! V eigenvector matrix. +! D eigenvalue diagonal matrix. +! +!----------------------------------------------------------------------- +! +subroutine ALHQR(ndim,n,A,maxiter,iter,V,D) + implicit none + !---- + ! Subroutine arguments + !---- + integer, intent(in) :: ndim,n,maxiter + integer, intent(out) :: iter + real(kind=8), dimension(ndim,n), intent(in) :: A + complex(kind=8), dimension(n,n), intent(out) :: V,D + !---- + ! Local variables + !---- + integer :: i,j,k,i1,i2,nset,ier,ii + complex(kind=8) :: kappa,p,qq,r,mu,csum,denom,m(2,2) + real(kind=8) :: sgn,tau,nu,normH,sum,AA(2,2),BB(2,2),CC(2,2) + real(kind=8), parameter :: eps=epsilon(A) + !---- + ! Allocatable arrays + !---- + integer, allocatable, dimension(:) :: iset + real(kind=8), allocatable, dimension(:) :: c + complex(kind=8), allocatable, dimension(:) :: s,t,work1d + real(kind=8), allocatable, dimension(:,:) :: VR + complex(kind=8), allocatable, dimension(:,:) :: H,Q,work2d + !---- + ! Perform Householder transformation to upper Hessenberg form + !---- + allocate(H(n,n), Q(n,n), VR(n,n-2)) + H(:n,:n)=A(:n,:n) + do k = 1,(n-2) + VR(k+1:n,k) = real(H(k+1:n,k)) + sgn = sign(1.0d0,VR(k+1,k)) + VR(k+1,k) = VR(k+1,k) + sgn * sqdotv(VR(k+1:n,k)) + sum = sqdotv(VR(k+1:n,k)) + if(sum /= 0.d0) VR(k+1:n,k) = VR(k+1:n,k) / sum + H(k+1:n,k:n) = H(k+1:n,k:n) - 2.0d0 * matmul(reshape(VR(k+1:n,k),(/n-k, 1/)), & + reshape(matmul(VR(k+1:n,k),H(k+1:n,k:n)),(/1, n-k+1/))) + H(:,k+1:n) = H(:,k+1:n) - 2.0d0 * matmul(reshape(matmul(H(:,k+1:n),VR(k+1:n,k)),(/n, 1/)), & + reshape(VR(k+1:n,k),(/1, n-k/))) + enddo + !---- + ! Construct Q matrix + !---- + Q(:n,:n) = 0.0D0 + do j=1,n + Q(j,j)=1.0d0 + enddo + do j = (n-2),1,-1 + Q(j+1:n,:n) = Q(j+1:n,:n) - 2.0d0 * matmul(reshape(VR(j+1:n,j),(/n-j, 1/)), & + reshape(matmul(VR(j+1:n,j),Q((j+1):n,:)),(/1, n/))) + enddo + deallocate(VR) + !---- + ! Perform Schur factorization + !---- + i2 = n + allocate(c(n),s(n),t(n)) + c(:n)=0.0d0; s(:n)=0.0d0; t(:n)=0.0d0; + iter = 0 + do + iter = iter + 1 + if(iter > maxiter) then + call xabort('ALHQR: maximum number of iterations exceeded.') + endif + ! Check subdiagonal for near zeros, deflating points. Finds deflating rows + ! on a complex Schur form matrix. + i1 = i2 + normH = sqdotm(abs(H(:n,:n))) + do + if(i1 == 1) exit + if(abs(H(i1,i1-1)) < eps*normH) then + H(i1,i1-1) = 0.0d0 + if(i1 == i2) then + i2 = i1 - 1; i1 = i1 - 1; + else + exit + endif + else + i1 = i1 - 1 + endif + enddo + !---- + ! End the function if H is upper triangular + !---- + if(i2 == 1) exit + ! Compute Wilkinson shift + kappa = H(i2,i2) + sum = abs(H(i2-1,i2-1)) + abs(H(i2-1,i2)) + abs(H(i2,i2-1)) + abs(H(i2,i2)) + if(sum /= 0) then + qq = (H(i2-1,i2)/sum)*(H(i2,i2-1)/sum) + if(qq /= 0) then + p = 0.5*((H(i2-1,i2-1)/sum) - (H(i2,i2)/sum)) + r = sqrt(p*p + qq); + if( (real(p)*real(r) + imag(p)*imag(r)) < 0 ) then + r = -r + endif + kappa = kappa - sum*(qq/(p+r)) + endif + endif + ! Apply shift to the element of the diagonal that is left out of the loop + H(i1,i1) = H(i1,i1) - kappa + do j = i1,i2-1 ! Loop reducing the matrix to triangular form + ! Apply Givens rotation so that the subdiagonal is set to zero + if(H(j+1,j) == 0) then + c(j) = 1.0d0; s(j) = 0.0d0; + elseif(H(j,j) == 0) then + c(j) = 0.0d0; s(j) = 1; H(j,j) = H(j+1,j); H(j+1,j) = 0.0d0; + else + mu = H(j,j)/abs(H(j,j)) + tau = abs(real(H(j,j))) + abs(imag(H(j,j))) + abs(real(H(j+1,j))) & + + abs(imag(H(j+1,j))) + nu = tau*sqrt(abs(H(j,j)/tau)**2 + abs(H(j+1,j)/tau)**2) + c(j) = abs(H(j,j))/nu + s(j) = mu*conjg(H(j+1,j))/nu + H(j,j) = nu*mu + H(j+1,j) = 0.0d0 + endif + ! Apply shift to diagonal + H(j+1,j+1) = H(j+1,j+1) - kappa + ! Modify the involved rows using a plane rotation + t(j+1:n) = c(j)*H(j,j+1:n) + s(j)*H(j+1,j+1:n) + H(j+1,j+1:n) = c(j)*H(j+1,j+1:n) - conjg(s(j))*H(j,j+1:n) + H(j,j+1:n) = t(j+1:n) + enddo + do k = i1,i2-1 + ! Loop applying the back multiplication using a plane rotation + t(1:k+1) = c(k)*H(1:k+1,k) + conjg(s(k))*H(1:k+1,k+1); + H(1:k+1,k+1) = c(k)*H(1:k+1,k+1) - s(k)*H(1:k+1,k) + H(1:k+1,k) = t(1:k+1) + ! Accumulate transformations using a plane rotation + t(1:n) = c(k)*Q(1:n,k) + conjg(s(k))*Q(1:n,k+1) + Q(1:n,k+1) = c(k)*Q(1:n,k+1) - s(k)*Q(1:n,k) + Q(1:n,k) = t(1:n) + H(k,k) = H(k,k) + kappa + enddo + H(i2,i2) = H(i2,i2) + kappa + enddo + deallocate(t,s,c) + !---- + ! Construct the orthonormal basis + !---- + V(:n,:n)=0.0d0 + D(:n,:n)=0.0d0 + do i=1,n + V(i,i)=1.0d0 + D(i,i)=H(i,i) + enddo + do j=2,n + do i=j-1,1,-1 + denom=H(i,i)-H(j,j) + if(denom /= 0) then + csum=0.0d0 + do k=i+1,j + csum=csum+H(i,k)*V(k,j) + enddo + V(i,j)=V(i,j)-csum/denom + endif + enddo + enddo + V=matmul(Q,V) + deallocate(Q,H) + !---- + ! Sort and normalize the eigensolution + !---- + allocate(iset(n),work1d(n),work2d(n,n)) + do i=1,n + work1d(i) = D(i,i) + enddo + call ALINDX(n, work1d, iset) + do i=1,n + work1d(i) = D(iset(i),iset(i)) + work2d(:n,i) = V(:n,iset(i)) + enddo + do i=1,n + D(i,i)=work1d(i) + enddo + V(:n,:n) = work2d(:n,:n) + deallocate(work2d,work1d) + nset=0 + do i=1,n + if(abs(imag(D(i,i))) > 1.0e-10) then + nset=nset+1 + iset(nset)=i + endif + enddo + do i=1,n + ii=findlc(iset(:nset),i) + if(mod(ii-1,2)+1.eq.1) then + j=iset(ii+1) + m=reshape( (/V(i,i), V(j,i), V(i,j), V(j,j)/), (/2, 2/) ) + m(:,1)=m(:,1)/sqdotv(abs(m(1:2,1))) + m(:,2)=m(:,2)/sqdotv(abs(m(1:2,2))) + AA=reshape( (/real(m(1,1))+real(m(2,1)), aimag(m(1,1))+aimag(m(2,1)), & + -aimag(m(1,1))-aimag(m(2,1)), real(m(1,1))+real(m(2,1)) /), (/2, 2/) ) + BB=reshape( (/real(m(1,2))+real(m(2,2)), -aimag(m(1,2))-aimag(m(2,2)), & + -aimag(m(1,2))-aimag(m(2,2)), -real(m(1,2))-real(m(2,2)) /), (/2, 2/) ) + call ALINVD(2,BB,2,ier) + if(ier.ne.0) call xabort('ALHQR: singular matrix') + CC=matmul(BB,AA) + V(:,i)=V(:,i)*cmplx(CC(1,1),CC(1,2),kind=8) + elseif (mod(ii-1,2)+1.eq.2) then + j=iset(ii-1) + if(abs(D(i,i)-conjg(D(j,j))) > 1.0e-10) then + call xabort('ALHQR: pathological ordering') + endif + D(i,i)=conjg(D(j,j)) + else + D(i,i)=real(D(i,i)) + endif + V(:,i)=V(:,i)/sqdotv(abs(V(:,i))) + enddo + deallocate(iset) + return + + contains + function sqdotv(vec) result(vsum) + ! function emulating the vectorial norm2 function in Fortran 2008 + real(kind=8), dimension(:), intent(in) :: vec + real(kind=8) :: vsum + vsum=sqrt(dot_product(vec(:),vec(:))) + end function sqdotv + function sqdotm(mat) result(vsum) + ! function emulating the matrix norm2 function in Fortran 2008 + real(kind=8), dimension(:,:), intent(in) :: mat + real(kind=8) :: vsum + vsum=0.0d0 + do i=1,size(mat,1) + do j=1,size(mat,2) + vsum=vsum+mat(i,j)**2 + enddo + enddo + vsum=sqrt(vsum) + end function sqdotm + function findlc(iset,itest) result(ii) + ! function emulating the findloc function in Fortran 2008 + integer, dimension(:), intent(in) :: iset + integer, intent(in) :: itest + integer :: ii + ii=0 + do j=1,size(iset) + if(iset(j) == itest) then + ii=j + exit + endif + enddo + end function findlc +end subroutine ALHQR |
