diff --git a/sorc/grid_tools.fd/regional_esg_grid.fd/psym2.f90 b/sorc/grid_tools.fd/regional_esg_grid.fd/psym2.f90 index 3fd573cb9..f8ed4293a 100644 --- a/sorc/grid_tools.fd/regional_esg_grid.fd/psym2.f90 +++ b/sorc/grid_tools.fd/regional_esg_grid.fd/psym2.f90 @@ -1,24 +1,21 @@ !> @file +!! @brief Matrix routines. !! @author R. J. Purser @date September 2018 + +!> A suite of routines to perform the eigen-decomposition of symmetric 2*2 +!! matrices and to deliver basic analytic functions, and the +!! derivatives of these functions, of such matrices. In addition, we +!! include a simple cholesky routine. !! -!! A suite of routines to perform the eigen-decomposition of symmetric 2*2 -!! matrices and to deliver basic analytic functions, and the derivatives -!! of these functions, of such matrices. -!! In addition, we include a simple cholesky routine -!! -!! DIRECT DEPENDENCIES -!! Library: pfun -!! Module: pkind, pietc, pfun -!! +!! @author R. J. Purser module psym2 -!============================================================================= use pkind, only: spi,dp use pietc, only: u0,u1,o2 implicit none private public:: eigensym2,invsym2,sqrtsym2,expsym2,logsym2,id2222,chol2 -real(dp),dimension(2,2,2,2):: id +real(dp),dimension(2,2,2,2):: id !< ID. data id/u1,u0,u0,u0, u0,o2,o2,u0, u0,o2,o2,u0, u0,u0,u0,u1/! Effective identity interface eigensym2; module procedure eigensym2,eigensym2d; end interface @@ -35,18 +32,18 @@ module psym2 contains -!============================================================================= +!> Get the orthogonal eigenvectors, vv, and diagonal matrix of +!! eigenvalues, oo, of the symmetric 2*2 matrix, em. +!! +!! @param[in] em symmetric 2*2 matrix +!! @param[out] vv orthogonal eigenvectors +!! @param[out] oo diagonal matrix of eigenvalues +!! @author R. J. Purser subroutine eigensym2(em,vv,oo)! [eigensym2] -!============================================================================= -! Get the orthogonal eigenvectors, vv, and diagonal matrix of eigenvalues, oo, -! of the symmetric 2*2 matrix, em. -!============================================================================= implicit none real(dp),dimension(2,2),intent(in ):: em real(dp),dimension(2,2),intent(out):: vv,oo -!----------------------------------------------------------------------------- real(dp):: a,b,c,d,e,f,g,h -!============================================================================= a=em(1,1); b=em(1,2); c=em(2,2) d=a*c-b*b! <- det(em) e=(a+c)*o2; f=(a-c)*o2 @@ -60,26 +57,30 @@ subroutine eigensym2(em,vv,oo)! [eigensym2] oo=matmul(transpose(vv),matmul(em,vv)) oo(1,2)=u0; oo(2,1)=u0 end subroutine eigensym2 -!============================================================================= + +!> For a symmetric 2*2 matrix, em, return the normalized eigenvectors, +!! vv, and the diagonal matrix of eigenvalues, oo. If the two +!! eigenvalues are equal, proceed no further and raise the logical +!! failure flag, ff, to .true.; otherwise, return with vvd=d(vv)/d(em) +!! and ood=d(oo)/d(em) and ff=.false., and maintain the symmetries +!! between the last two of the indices of these derivatives. +!! +!! @param[in] em symmetric 2*2 matrix +!! @param[out] vv normalized eigenvectors +!! @param[out] oo diagonal matrix of eigenvalues +!! @param[out] vvd vvd=d(vv)/d(em) +!! @param[out] ood ood=d(oo)/d(em) +!! @param[out] ff logical failure flag +!! @author R. J. Purser subroutine eigensym2d(em,vv,oo,vvd,ood,ff)! [eigensym2] -!============================================================================= -! For a symmetric 2*2 matrix, em, return the normalized eigenvectors, vv, and -! the diagonal matrix of eigenvalues, oo. If the two eigenvalues are equal, -! proceed no further and raise the logical failure flag, ff, to .true.; -! otherwise, return with vvd=d(vv)/d(em) and ood=d(oo)/d(em) and ff=.false., -! and maintain the symmetries between the last two of the indices of -! these derivatives. -!============================================================================= implicit none real(dp),dimension(2,2), intent(in ):: em real(dp),dimension(2,2), intent(out):: vv,oo real(dp),dimension(2,2,2,2),intent(out):: vvd,ood logical, intent(out):: ff -!----------------------------------------------------------------------------- real(dp),dimension(2,2):: emd,tt,vvr real(dp) :: oodif,dtheta integer(spi) :: i,j -!============================================================================= call eigensym2(em,vv,oo); vvr(1,:)=-vv(2,:); vvr(2,:)=vv(1,:) oodif=oo(1,1)-oo(2,2); ff=oodif==u0; if(ff)return ood=0 @@ -101,37 +102,38 @@ subroutine eigensym2d(em,vv,oo,vvd,ood,ff)! [eigensym2] enddo end subroutine eigensym2d -!============================================================================= +!> Get the inverse of a 2*2 matrix (need not be symmetric in this +!! case). +!! +!! @param[in] em 2*2 matrix +!! @param[out] z inverse of a 2*2 matrix +!! @author R. J. Purser subroutine invsym2(em,z)! [invsym2] -!============================================================================= -! Get the inverse of a 2*2 matrix (need not be symmetric in this case). -!============================================================================= implicit none real(dp),dimension(2,2),intent(in ):: em real(dp),dimension(2,2),intent(out):: z -!----------------------------------------------------------------------------- real(dp):: detem -!============================================================================= z(1,1)=em(2,2); z(2,1)=-em(2,1); z(1,2)=-em(1,2); z(2,2)=em(1,1) detem=em(1,1)*em(2,2)-em(2,1)*em(1,2) z=z/detem end subroutine invsym2 -!============================================================================= + +!> Get the inverse, z,of a 2*2 symmetric matrix, em, and its +!! derivative, zd, with respect to symmetric variations of its +!! components. I.e., for a symmetric infinitesimal change, delta_em, +!! in em, the resulting infinitesimal change in z would be: +!!
delta_z(i,j) = matmul(zd(i,j,:,:),delta_em)+!! +!! @param[in] em 2*2 symmetric matrix +!! @param[out] z inverse of a 2*2 symmetric matrix +!! @param[out] zd derivative of the 2*2 symmetric matrix +!! @author R. J. Purser subroutine invsym2d(em,z,zd)! [invsym2] -!============================================================================= -! Get the inverse, z,of a 2*2 symmetric matrix, em, and its derivative, zd, -! with respect to symmetric variations of its components. I.e., for a -! symmetric infinitesimal change, delta_em, in em, the resulting -! infinitesimal change in z would be: -! delta_z(i,j) = matmul(zd(i,j,:,:),delta_em) -!============================================================================= implicit none real(dp),dimension(2,2) ,intent(in ):: em real(dp),dimension(2,2) ,intent(out):: z real(dp),dimension(2,2,2,2),intent(out):: zd -!----------------------------------------------------------------------------- integer(spi):: k,l -!============================================================================= call invsym2(em,z) call id2222(zd) do l=1,2; do k=1,2 @@ -139,18 +141,17 @@ subroutine invsym2d(em,z,zd)! [invsym2] enddo; enddo end subroutine invsym2d -!============================================================================= +!> Get the sqrt of a symmetric positive-definite 2*2 matrix. +!! +!! @param[in] em 2*2 symmetric matrix +!! @param[out] z sqrt of a symmetric positive-definite 2*2 matrix +!! @author R. J. Purser subroutine sqrtsym2(em,z)! [sqrtsym2] -!============================================================================= -! Get the sqrt of a symmetric positive-definite 2*2 matrix -!============================================================================= implicit none real(dp),dimension(2,2),intent(in ):: em real(dp),dimension(2,2),intent(out):: z -!----------------------------------------------------------------------------- real(dp),dimension(2,2):: vv,oo integer(spi) :: i -!============================================================================= call eigensym2(em,vv,oo) do i=1,2 if(oo(i,i)<0)stop 'In sqrtsym2; matrix em is not non-negative' @@ -158,52 +159,55 @@ subroutine sqrtsym2(em,z)! [sqrtsym2] z=matmul(vv,matmul(oo,transpose(vv))) end subroutine sqrtsym2 -!============================================================================= +!> General routine to evaluate z=sqrt(x), and the symmetric +!! derivative, zd = dz/dx, where x is a symmetric 2*2 +!! positive-definite matrix. If the eigenvalues are very close +!! together, extract their geometric mean for "preconditioning" a +!! scaled version, px, of x, whose sqrt, and hence its derivative, can +!! be easily obtained by the series expansion method. Otherwise, use +!! the eigen-method (which entails dividing by the difference in the +!! eignevalues to get zd, and which therefore fails when the +!! eigenvalues become too similar). +!! +!! @param[in] x symmetric 2*2 positive-definite matrix +!! @param[out] z sqrt(x) result +!! @param[out] zd symmetric derivative +!! @author R. J. Purser subroutine sqrtsym2d(x,z,zd)! [sqrtsym2] -!============================================================================= -! General routine to evaluate z=sqrt(x), and the symmetric -! derivative, zd = dz/dx, where x is a symmetric 2*2 positive-definite -! matrix. If the eigenvalues are very close together, extract their -! geometric mean for "preconditioning" a scaled version, px, of x, whose -! sqrt, and hence its derivative, can be easily obtained by the series -! expansion method. Otherwise, use the eigen-method (which entails dividing -! by the difference in the eignevalues to get zd, and which therefore -! fails when the eigenvalues become too similar). -!============================================================================= implicit none real(dp),dimension(2,2), intent(in ):: x real(dp),dimension(2,2), intent(out):: z real(dp),dimension(2,2,2,2),intent(out):: zd -!----------------------------------------------------------------------------- real(dp),dimension(2,2):: px real(dp) :: rdetx,lrdetx,htrpxs,q -!============================================================================= rdetx=sqrt(x(1,1)*x(2,2)-x(1,2)*x(2,1)) ! <- sqrt(determinant of x) lrdetx=sqrt(rdetx) -px=x/rdetx ! <- preconditioned x (has unit determinant) -htrpxs= ((px(1,1)+px(2,2))/2)**2 ! <- {half-trace-px}-squared +px=x/rdetx ! - preconditioned x (has unit determinant) +htrpxs= ((px(1,1)+px(2,2))/2)**2 ! - {half-trace-px}-squared q=htrpxs-u1 -if(q<.05_dp)then ! <- Taylor expansion method +if(q<.05_dp)then ! - Taylor expansion method call sqrtsym2d_t(px,z,zd) z=z*lrdetx; zd=zd/lrdetx else - call sqrtsym2d_e(x,z,zd) ! <- Eigen-method + call sqrtsym2d_e(x,z,zd) ! - Eigen-method endif end subroutine sqrtsym2d -!============================================================================= +!> Eigen-method. +!! +!! @param[in] x symmetric 2*2 positive-definite matrix +!! @param[out] z sqrt(x) result +!! @param[out] zd symmetric derivative +!! @author R. J. Purser subroutine sqrtsym2d_e(x,z,zd)! [sqrtsym2d_e] -!============================================================================= implicit none real(dp),dimension(2,2), intent(in ):: x real(dp),dimension(2,2), intent(out):: z real(dp),dimension(2,2,2,2),intent(out):: zd -!----------------------------------------------------------------------------- real(dp),dimension(2,2,2,2):: vvd,ood real(dp),dimension(2,2) :: vv,oo,oori,tt integer(spi) :: i,j logical :: ff -!============================================================================= call eigensym2(x,vv,oo,vvd,ood,ff) z=u0; z(1,1)=sqrt(oo(1,1)); z(2,2)=sqrt(oo(2,2)) z=matmul(matmul(vv,z),transpose(vv)) @@ -217,26 +221,27 @@ subroutine sqrtsym2d_e(x,z,zd)! [sqrtsym2d_e] enddo end subroutine sqrtsym2d_e -!============================================================================= +!> Use the Taylor-series method (eigenvalues both fairly close to unity). +!! For a 2*2 positive definite symmetric matrix x, try to get both the +!! z=sqrt(x) and dz/dx using the binomial-expansion method applied to +!! the intermediate matrix, +!!
r = (x-1). ie z=sqrt(x) = (1+r)^{1/2} = I + (1/2)*r -(1/8)*r^2 ...
+!! + [(-)^n *(2n)!/{(n+1)! * n! *2^{2*n-1}} ]*r^{n+1}
+!!
+!! @param[in] x symmetric 2*2 positive-definite matrix
+!! @param[out] z sqrt(x) result
+!! @param[out] zd symmetric derivative
+!! @author R. J. Purser
subroutine sqrtsym2d_t(x,z,zd)! [sqrtsym2d_t]
-!=============================================================================
-! Use the Taylor-series method (eigenvalues both fairly close to unity).
-! For a 2*2 positive definite symmetric matrix x, try to get both the z=sqrt(x)
-! and dz/dx using the binomial-expansion method applied to the intermediate
-! matrix, r = (x-1). ie z=sqrt(x) = (1+r)^{1/2} = I + (1/2)*r -(1/8)*r^2 ...
-! + [(-)^n *(2n)!/{(n+1)! * n! *2^{2*n-1}} ]*r^{n+1}
-!=============================================================================
implicit none
real(dp),dimension(2,2), intent(in ):: x
real(dp),dimension(2,2), intent(out):: z
real(dp),dimension(2,2,2,2),intent(out):: zd
-!-----------------------------------------------------------------------------
integer(spi),parameter :: nit=300 ! number of iterative increments allowed
real(dp),parameter :: crit=1.e-17
real(dp),dimension(2,2) :: r,rp,rd,rpd
real(dp) :: c
integer(spi) :: i,j,n
-!=============================================================================
r=x; r(1,1)=x(1,1)-1; r(2,2)=x(2,2)-1
z=u0; z(1,1)=u1; z(2,2)=u1
rp=r
@@ -263,33 +268,35 @@ subroutine sqrtsym2d_t(x,z,zd)! [sqrtsym2d_t]
enddo; enddo
end subroutine sqrtsym2d_t
-!=============================================================================
+!> Get the exp of a symmetric 2*2 matrix.
+!!
+!! @param[in] em symmetric 2*2 matrix
+!! @param[out] expem exp of a symmetric 2*2 matrix
+!! @author R. J. Purser
subroutine expsym2(em,expem)! [expsym2]
-!=============================================================================
-! Get the exp of a symmetric 2*2 matrix
-!=============================================================================
implicit none
real(dp),dimension(2,2),intent(in ):: em
real(dp),dimension(2,2),intent(out):: expem
-!-----------------------------------------------------------------------------
real(dp),dimension(2,2):: vv,oo
integer(spi) :: i
-!=============================================================================
call eigensym2(em,vv,oo)
do i=1,2; oo(i,i)=exp(oo(i,i)); enddo
expem=matmul(vv,matmul(oo,transpose(vv)))
end subroutine expsym2
-!=============================================================================
+
+!> Sub Process for process symmetric 2*2 matrix.
+!!
+!! @param[in] x symmetric 2*2 positive-definite matrix
+!! @param[out] z ???
+!! @param[out] zd symmetric derivative
+!! @author R. J. Purser
subroutine expsym2d(x,z,zd)! [expsym2]
-!=============================================================================
implicit none
real(dp),dimension(2,2), intent(in ):: x
real(dp),dimension(2,2), intent(out):: z
real(dp),dimension(2,2,2,2),intent(out):: zd
-!-----------------------------------------------------------------------------
real(dp),dimension(2,2):: px
real(dp) :: trxh,detpx
-!=============================================================================
trxh=(x(1,1)+x(2,2))*o2
px=x;px(1,1)=x(1,1)-trxh;px(2,2)=x(2,2)-trxh
detpx=abs(px(1,1)*px(2,2)-px(1,2)*px(2,1))
@@ -299,19 +306,21 @@ subroutine expsym2d(x,z,zd)! [expsym2]
z=z*exp(trxh)
end subroutine expsym2d
-!=============================================================================
+!> Sub Process for process symmetric 2*2 matrix.
+!!
+!! @param[in] x symmetric 2*2 positive-definite matrix
+!! @param[out] z ???
+!! @param[out] zd symmetric derivative
+!! @author R. J. Purser
subroutine expsym2d_e(x,z,zd)! [expsym2d_e]
-!=============================================================================
implicit none
real(dp),dimension(2,2), intent(in ):: x
real(dp),dimension(2,2), intent(out):: z
real(dp),dimension(2,2,2,2),intent(out):: zd
-!-----------------------------------------------------------------------------
real(dp),dimension(2,2,2,2):: vvd,ood
real(dp),dimension(2,2) :: vv,oo,ooe,tt
integer(spi) :: i,j
logical :: ff
-!=============================================================================
call eigensym2(x,vv,oo,vvd,ood,ff)
z=u0; z(1,1)=exp(oo(1,1)); z(2,2)=exp(oo(2,2))
z=matmul(matmul(vv,z),transpose(vv))
@@ -325,24 +334,24 @@ subroutine expsym2d_e(x,z,zd)! [expsym2d_e]
enddo
end subroutine expsym2d_e
-!=============================================================================
+!> Use the Taylor-series method (eigenvalues both fairly close to
+!! zero). For a 2*2 symmetric matrix x, try to get both the z=exp(x)
+!! and dz/dx using the Taylor series expansion method.
+!!
+!! @param[in] x symmetric 2*2 positive-definite matrix
+!! @param[out] z Taylor series expansion method exp(x)
+!! @param[out] zd symmetric derivative
+!! @author R. J. Purser
subroutine expsym2d_t(x,z,zd)! [expsym2d_t]
-!=============================================================================
-! Use the Taylor-series method (eigenvalues both fairly close to zero).
-! For a 2*2 symmetric matrix x, try to get both the z=exp(x)
-! and dz/dx using the Taylor series expansion method.
-!=============================================================================
implicit none
real(dp),dimension(2,2), intent(in ):: x
real(dp),dimension(2,2), intent(out):: z
real(dp),dimension(2,2,2,2),intent(out):: zd
-!-----------------------------------------------------------------------------
integer(spi),parameter :: nit=100 ! number of iterative increments allowed
real(dp),parameter :: crit=1.e-17_dp
real(dp),dimension(2,2) :: xp,xd,xpd
real(dp) :: c
integer(spi) :: i,j,n
-!=============================================================================
z=0; z(1,1)=u1; z(2,2)=u1
xp=x
c=u1
@@ -368,18 +377,17 @@ subroutine expsym2d_t(x,z,zd)! [expsym2d_t]
enddo; enddo
end subroutine expsym2d_t
-!=============================================================================
+!> Get the log of a symmetric positive-definite 2*2 matrix.
+!!
+!! @param[in] em symmetric 2*2 matrix
+!! @param[out] logem log of a symmetric positive-definite 2*2 matrix
+!! @author R. J. Purser
subroutine logsym2(em,logem)! [logsym2]
-!=============================================================================
-! Get the log of a symmetric positive-definite 2*2 matrix
-!=============================================================================
implicit none
real(dp),dimension(2,2),intent(in ):: em
real(dp),dimension(2,2),intent(out):: logem
-!-----------------------------------------------------------------------------
real(dp),dimension(2,2):: vv,oo
integer(spi) :: i
-!=============================================================================
call eigensym2(em,vv,oo)
do i=1,2
if(oo(i,i)<=u0)stop 'In logsym2; matrix em is not positive definite'
@@ -387,23 +395,24 @@ subroutine logsym2(em,logem)! [logsym2]
enddo
logem=matmul(vv,matmul(oo,transpose(vv)))
end subroutine logsym2
-!=============================================================================
+
+!> General routine to evaluate the logarithm, z=log(x), and the
+!! symmetric derivative, zd = dz/dx, where x is a symmetric 2*2
+!! positive-definite matrix.
+!!
+!! @param[in] zd the symmetric derivative
+!! @param[out] x a symmetric 2*2 positive-definite matrix
+!! @param[out] z evaluate the logarithm log(x)
+!! @author R. J. Purser
subroutine logsym2d(x,z,zd)! [logsym2]
-!=============================================================================
-! General routine to evaluate the logarithm, z=log(x), and the symmetric
-! derivative, zd = dz/dx, where x is a symmetric 2*2 positive-definite
-! matrix.
-!=============================================================================
use pfun, only: sinhox
implicit none
real(dp),dimension(2,2), intent(in ):: x
real(dp),dimension(2,2), intent(out):: z
real(dp),dimension(2,2,2,2),intent(out):: zd
-!-----------------------------------------------------------------------------
real(dp),dimension(2,2):: vv,oo,d11,d12,d22,pqr
real(dp) :: c,s,cc,cs,ss,c2h,p,q,r,lp,lq,L
integer(spi) :: i
-!=============================================================================
call eigensym2(x,vv,oo)
if(oo(1,1)<=u0 .or. oo(2,2)<=u0)stop 'In logsym2; matrix x is not positive definite'
c=vv(1,1); s=vv(1,2); cc=c*c; cs=c*s; ss=s*s; c2h=(cc-ss)*o2
@@ -420,9 +429,11 @@ subroutine logsym2d(x,z,zd)! [logsym2]
zd(:,:,2,1)=zd(:,:,1,2)
end subroutine logsym2d
-!=============================================================================
+!> General routine for Effective identity.
+!!
+!! @param[out] em ???
+!! @author R. J. Purser
subroutine id2222(em)! [id2222]
-!=============================================================================
implicit none
real(dp),dimension(2,2,2,2),intent(out):: em
real(dp),dimension(2,2,2,2) :: id
@@ -430,20 +441,21 @@ subroutine id2222(em)! [id2222]
em=id
end subroutine id2222
-!===========================================================================
+!> Return the cholesky lower triangular factor, C, of the 2X2
+!! symmetric matrix, S, or raise the failure flag, FF, if S is not
+!! positive-definite.
+!!
+!! @param[in] s 2X2 symmetric matrix
+!! @param[out] c cholesky lower triangular factor
+!! @param[out] ff raise the failure flag
+!! @author R. J. Purser
subroutine chol2(s,c,ff)! [chol2]
-!===========================================================================
-! Return the cholesky lower triangular factor, C, of the 2X2 symmetric
-! matrix, S, or raise the failure flag, FF, if S is not positive-definite.
-!===========================================================================
use pietc, only: u0
implicit none
real(dp),dimension(2,2),intent(in ):: s
real(dp),dimension(2,2),intent(out):: c
logical ,intent(out):: ff
-!---------------------------------------------------------------------------
real(dp):: r
-!===========================================================================
ff=s(1,1)<=u0; if(ff)return
c(1,1)=sqrt(s(1,1))
c(1,2)=u0