!-----------------------------------------------------------------------
! $Id: lapack_wrap.F90 6849 2014-04-22 21:52:30Z charlass@uwm.edu $
!===============================================================================
module lapack_wrap
! Description:
! Wrappers for the band diagonal and tridiagonal direct matrix
! solvers contained in the LAPACK library.
! References:
! LAPACK--Linear Algebra PACKage
! URL:
!-----------------------------------------------------------------------
use constants_clubb, only: &
fstderr ! Variable(s)
use error_code, only: &
clubb_singular_matrix, & ! Variable(s)
clubb_bad_lapack_arg, &
clubb_var_equals_NaN, &
clubb_no_error
use clubb_precision, only: &
core_rknd, & ! Variable(s)
dp
implicit none
! Simple routines
public :: tridag_solve, band_solve
! Expert routines
public :: tridag_solvex, band_solvex
private :: lapack_isnan
! A best guess for what the precision of a single precision and double
! precision float is in LAPACK. Hopefully this will work more portably on
! architectures like Itanium than the old code -dschanen 11 Aug 2011
integer, parameter, private :: &
sp = kind ( 0.0 )
private ! Set Default Scope
contains
!-----------------------------------------------------------------------
subroutine tridag_solvex( solve_type, ndim, nrhs, &
supd, diag, subd, rhs, &
solution, rcond, err_code )
! Description:
! Solves a tridiagonal system of equations (expert routine).
! References:
!
!
! Notes:
! More expensive than the simple routine, but tridiagonal
! decomposition is still relatively cheap.
!-----------------------------------------------------------------------
use error_code, only: &
clubb_at_least_debug_level ! Logical function
use clubb_precision, only: &
core_rknd ! Variable(s)
implicit none
! External
external :: &
sgtsvx, & ! Single-prec. General Tridiagonal Solver eXpert
dgtsvx ! Double-prec. General Tridiagonal Solver eXpert
intrinsic :: kind
! Input variables
character(len=*), intent(in) :: &
solve_type ! Used to write a message if this fails
integer, intent(in) :: &
ndim, & ! N-dimension of matrix
nrhs ! # of right hand sides to back subst. after LU-decomp.
! Input/Output variables
real( kind = core_rknd ), intent(inout), dimension(ndim) :: &
diag, & ! Main diagonal
subd, supd ! Sub and super diagonal
real( kind = core_rknd ), intent(inout), dimension(ndim,nrhs) :: &
rhs ! RHS input
! The estimate of the reciprocal of the condition number on the LHS matrix.
! If rcond is < machine precision the matrix is singular to working
! precision, and info == ndim+1. If rcond == 0, then the LHS matrix
! is singular. This condition is indicated by a return code of info > 0.
real( kind = core_rknd ), intent(out) :: rcond
integer, intent(out) :: &
err_code ! Used to determine when a decomp. failed
! Output variables
real( kind = core_rknd ), intent(out), dimension(ndim,nrhs) :: &
solution ! Solution
! Local Variables
! These contain the decomposition of the matrix
real( kind = core_rknd ), dimension(ndim-1) :: dlf, duf
real( kind = core_rknd ), dimension(ndim) :: df
real( kind = core_rknd ), dimension(ndim-2) :: du2
integer, dimension(ndim) :: &
ipivot ! Index of pivots done during decomposition
integer, dimension(ndim) :: &
iwork ! `scrap' array
real( kind = core_rknd ), dimension(nrhs) :: &
ferr, & ! Forward error estimate
berr ! Backward error estimate
real( kind = core_rknd ), dimension(3*ndim) :: &
work ! `Scrap' array
integer :: info ! Diagnostic output
integer :: i ! Array index
!-----------------------------------------------------------------------
! *** The LAPACK Routine ***
! SUBROUTINE SGTSVX( FACT, TRANS, N, NRHS, DL, D, DU, DLF, DF, DUF,
! $ DU2, IPIV, B, LDB, X, LDX, RCOND, FERR, BERR,
! $ WORK, IWORK, INFO )
!-----------------------------------------------------------------------
if ( kind( diag(1) ) == dp ) then
call dgtsvx( "Not Factored", "No Transpose lhs", ndim, nrhs, &
subd(2:ndim), diag, supd(1:ndim-1), &
dlf, df, duf, du2, ipivot, &
rhs, ndim, solution, ndim, rcond, &
ferr, berr, work, iwork, info )
else if ( kind( diag(1) ) == sp ) then
call sgtsvx( "Not Factored", "No Transpose lhs", ndim, nrhs, &
subd(2:ndim), diag, supd(1:ndim-1), &
dlf, df, duf, du2, ipivot, &
rhs, ndim, solution, ndim, rcond, &
ferr, berr, work, iwork, info )
else
stop "tridag_solvex: Cannot resolve the precision of real datatype"
end if
! Print diagnostics for when ferr is large
if ( clubb_at_least_debug_level( 2 ) .and. any( ferr > 1.e-3_core_rknd ) ) then
write(fstderr,*) "Warning, large error est. for: " // trim( solve_type )
do i = 1, nrhs, 1
write(fstderr,*) "rhs # ", i, "tridag forward error est. =", ferr(i)
write(fstderr,*) "rhs # ", i, "tridag backward error est. =", berr(i)
end do
write(fstderr,'(2(a20,e15.6))') "rcond est. = ", rcond, &
"machine epsilon = ", epsilon( diag(1) )
end if
select case( info )
case( :-1 )
write(fstderr,*) trim( solve_type )// &
"illegal value in argument", -info
err_code = clubb_bad_lapack_arg
case( 0 )
! Success!
if ( lapack_isnan( ndim, nrhs, solution ) ) then
err_code = clubb_var_equals_NaN
else
err_code = clubb_no_error
end if
case( 1: )
if ( info == ndim+1 ) then
write(fstderr,*) trim( solve_type) // &
" Warning: matrix is singular to working precision."
write(fstderr,'(a,e12.5)') &
"Estimate of the reciprocal of the condition number: ", rcond
err_code = clubb_no_error
else
write(fstderr,*) solve_type// &
" singular matrix."
err_code = clubb_singular_matrix
end if
end select
return
end subroutine tridag_solvex
!-----------------------------------------------------------------------
subroutine tridag_solve &
( solve_type, ndim, nrhs, &
supd, diag, subd, rhs, &
solution, err_code )
! Description:
! Solves a tridiagonal system of equations (simple routine)
! References:
!
!
!-----------------------------------------------------------------------
use clubb_precision, only: &
core_rknd ! Variable(s)
implicit none
! External
external :: &
sgtsv, & ! Single-prec. General Tridiagonal Solver eXpert
dgtsv ! Double-prec. General Tridiagonal Solver eXpert
intrinsic :: kind
! Input variables
character(len=*), intent(in) :: &
solve_type ! Used to write a message if this fails
integer, intent(in) :: &
ndim, & ! N-dimension of matrix
nrhs ! # of right hand sides to back subst. after LU-decomp.
! Input/Output variables
real( kind = core_rknd ), intent(inout), dimension(ndim) :: &
diag, & ! Main diagonal
subd, supd ! Sub and super diagonal
real( kind = core_rknd ), intent(inout), dimension(ndim,nrhs) :: &
rhs ! RHS input
! Output variables
real( kind = core_rknd ), intent(out), dimension(ndim,nrhs) :: &
solution ! Solution
integer, intent(out) :: &
err_code ! Used to determine when a decomp. failed
! Local Variables
real( kind = dp ), dimension(ndim) :: &
subd_dp, supd_dp, diag_dp
real( kind = dp ), dimension(ndim,nrhs) :: &
rhs_dp
integer :: info ! Diagnostic output
!-----------------------------------------------------------------------
! *** The LAPACK Routine ***
! SUBROUTINE DGTSV( N, NRHS, DL, D, DU, B, LDB, INFO )
!-----------------------------------------------------------------------
if ( kind( diag(1) ) == dp ) then
call dgtsv( ndim, nrhs, subd(2:ndim), diag, supd(1:ndim-1), &
rhs, ndim, info )
else if ( kind( diag(1) ) == sp ) then
call sgtsv( ndim, nrhs, subd(2:ndim), diag, supd(1:ndim-1), &
rhs, ndim, info )
else
!stop "tridag_solve: Cannot resolve the precision of real datatype"
! Eric Raut Aug 2013: Force double precision
subd_dp = real( subd, kind=dp )
diag_dp = real( diag, kind=dp )
supd_dp = real( supd, kind=dp )
rhs_dp = real( rhs, kind=dp )
call dgtsv( ndim, nrhs, subd_dp(2:ndim), diag_dp, supd_dp(1:ndim-1), &
rhs_dp, ndim, info )
subd = real( subd_dp, kind=core_rknd )
diag = real( diag_dp, kind=core_rknd )
supd = real( supd_dp, kind=core_rknd )
rhs = real( rhs_dp, kind=core_rknd )
end if
select case( info )
case( :-1 )
write(fstderr,*) trim( solve_type )// &
" illegal value in argument", -info
err_code = clubb_bad_lapack_arg
solution = -999._core_rknd
case( 0 )
! Success!
if ( lapack_isnan( ndim, nrhs, rhs ) ) then
err_code = clubb_var_equals_NaN
else
err_code = clubb_no_error
end if
solution = rhs
case( 1: )
write(fstderr,*) trim( solve_type )//" singular matrix."
err_code = clubb_singular_matrix
solution = -999._core_rknd
end select
return
end subroutine tridag_solve
!-----------------------------------------------------------------------
subroutine band_solvex( solve_type, nsup, nsub, ndim, nrhs, &
lhs, rhs, solution, rcond, err_code )
! Description:
! Restructure and then solve a band diagonal system, with
! diagnostic output
! References:
!
!
! Notes:
! I found that due to the use of sgbcon/dgbcon it is much
! more expensive to use this on most systems than the simple
! driver. Use this version only if you don't case about compute time.
! Also note that this version equilibrates the lhs and does an iterative
! refinement of the solutions, which results in a slightly different answer
! than the simple driver does. -dschanen 24 Sep 2008
!-----------------------------------------------------------------------
use error_code, only: &
clubb_at_least_debug_level ! Logical function
use clubb_precision, only: &
core_rknd ! Variable(s)
implicit none
! External
external :: &
sgbsvx, & ! Single-prec. General Band Solver eXpert
dgbsvx ! Double-prec. General Band Solver eXpert
intrinsic :: eoshift, kind, trim
! Input Variables
character(len=*), intent(in) :: solve_type
integer, intent(in) :: &
nsup, & ! Number of superdiagonals
nsub, & ! Number of subdiagonals
ndim, & ! The order of the LHS Matrix, i.e. the # of linear equations
nrhs ! Number of RHS's to back substitute for
real( kind = core_rknd ), dimension(nsup+nsub+1,ndim), intent(inout) :: &
lhs ! Left hand side
real( kind = core_rknd ), dimension(ndim,nrhs), intent(inout) :: &
rhs ! Right hand side(s)
! Output Variables
real( kind = core_rknd ), dimension(ndim,nrhs), intent(out) :: &
solution
! The estimate of the reciprocal condition number of matrix
! after equilibration (if done).
real( kind = core_rknd ), intent(out) :: &
rcond
integer, intent(out) :: err_code ! Valid calculation?
! Local Variables
! Workspaces
real( kind = core_rknd ), dimension(3*ndim) :: work
integer, dimension(ndim) :: iwork
real( kind = core_rknd ), dimension(2*nsub+nsup+1,ndim) :: &
lulhs ! LU Decomposition of the LHS
integer, dimension(ndim) :: &
ipivot
real( kind = core_rknd ), dimension(nrhs) :: &
ferr, berr ! Forward and backward error estimate
real( kind = core_rknd ), dimension(ndim) :: &
rscale, cscale ! Row and column scale factors for the LHS
integer :: &
info, & ! If this doesn't come back as 0, something went wrong
offset, & ! Loop iterator
imain, & ! Main diagonal of the matrix
i ! Loop iterator
character :: &
equed ! Row equilibration status
!-----------------------------------------------------------------------
! Reorder Matrix to use LAPACK band matrix format (5x6)
! Shift example:
! [ * * lhs(1,1) lhs(1,2) lhs(1,3) lhs(1,4) ] (2)=>
! [ * lhs(2,1) lhs(2,2) lhs(2,3) lhs(2,4) lhs(2,5) ] (1)=>
! [ lhs(3,1) lhs(3,2) lhs(3,3) lhs(3,4) lhs(3,5) lhs(3,6) ]
! <=(1) [ lhs(4,2) lhs(4,3) lhs(4,4) lhs(4,5) lhs(4,6) * ]
! <=(2) [ lhs(5,3) lhs(5,4) lhs(5,5) lhs(5,6) * * ]
! The '*' indicates unreferenced elements.
! For additional bands above and below the main diagonal, the
! shifts to the left or right increases by the distance from the
! main diagonal of the matrix.
!-----------------------------------------------------------------------
imain = nsup + 1
! For the offset, (+) is left, and (-) is right
! Sub diagonals
do offset = 1, nsub, 1
lhs(imain+offset, 1:ndim) &
= eoshift( lhs(imain+offset, 1:ndim), offset )
end do
! Super diagonals
do offset = 1, nsup, 1
lhs(imain-offset, 1:ndim) &
= eoshift( lhs(imain-offset, 1:ndim), -offset )
end do
!-----------------------------------------------------------------------
! *** The LAPACK Routine ***
! SUBROUTINE SGBSVX( FACT, TRANS, N, KL, KU, NRHS, AB, LDAB, AFB,
! $ LDAFB, IPIV, EQUED, R, C, B, LDB, X, LDX,
! $ RCOND, FERR, BERR, WORK, IWORK, INFO )
!-----------------------------------------------------------------------
if ( kind( lhs(1,1) ) == dp ) then
call dgbsvx( 'Equilibrate lhs', 'No Transpose lhs', &
ndim, nsub, nsup, nrhs, &
lhs, nsup+nsub+1, lulhs, 2*nsub+nsup+1, &
ipivot, equed, rscale, cscale, &
rhs, ndim, solution, ndim, &
rcond, ferr, berr, work, iwork, info )
else if ( kind( lhs(1,1) ) == sp ) then
call sgbsvx( 'Equilibrate lhs', 'No Transpose lhs', &
ndim, nsub, nsup, nrhs, &
lhs, nsup+nsub+1, lulhs, 2*nsub+nsup+1, &
ipivot, equed, rscale, cscale, &
rhs, ndim, solution, ndim, &
rcond, ferr, berr, work, iwork, info )
else
stop "band_solvex: Cannot resolve the precision of real datatype"
! One implication of this is that CLUBB cannot be used with quad
! precision variables without a quad precision band diagonal solver
end if
! %% debug
! select case ( equed )
! case ('N')
! print *, "No equilib. was required for lhs."
! case ('R')
! print *, "Row equilib. was done on lhs."
! case ('C')
! print *, "Column equilib. was done on lhs."
! case ('B')
! print *, "Row and column equilib. was done on lhs."
! end select
! write(*,'(a,e12.5)') "Row scale : ", rscale
! write(*,'(a,e12.5)') "Column scale: ", cscale
! write(*,'(a,e12.5)') "Estimate of the reciprocal of the "//
! "condition number: ", rcond
! write(*,'(a,e12.5)') "Forward Error Estimate: ", ferr
! write(*,'(a,e12.5)') "Backward Error Estimate: ", berr
! %% end debug
! Diagnostic information
if ( clubb_at_least_debug_level( 2 ) .and. any( ferr > 1.e-3_core_rknd ) ) then
write(fstderr,*) "Warning, large error est. for: " // trim( solve_type )
do i = 1, nrhs, 1
write(fstderr,*) "rhs # ", i, "band_solvex forward error est. =", ferr(i)
write(fstderr,*) "rhs # ", i, "band_solvex backward error est. =", berr(i)
end do
write(fstderr,'(2(a20,e15.6))') "rcond est. = ", rcond, &
"machine epsilon = ", epsilon( lhs(1,1) )
end if
select case( info )
case( :-1 )
write(fstderr,*) trim( solve_type )// &
" illegal value for argument", -info
err_code = clubb_bad_lapack_arg
case( 0 )
! Success!
if ( lapack_isnan( ndim, nrhs, solution ) ) then
err_code = clubb_var_equals_NaN
else
err_code = clubb_no_error
end if
case( 1: )
if ( info == ndim+1 ) then
write(fstderr,*) trim( solve_type )// &
" Warning: matrix singular to working precision."
write(fstderr,'(a,e12.5)') &
"Estimate of the reciprocal of the"// &
" condition number: ", rcond
err_code = clubb_no_error
else
write(fstderr,*) trim( solve_type )// &
" band solver: singular matrix"
err_code = clubb_singular_matrix
end if
end select
return
end subroutine band_solvex
!-----------------------------------------------------------------------
subroutine band_solve( solve_type, nsup, nsub, ndim, nrhs, &
lhs, rhs, solution, err_code )
! Description:
! Restructure and then solve a band diagonal system
! References:
!
!
!-----------------------------------------------------------------------
use clubb_precision, only: &
core_rknd ! Variable(s)
implicit none
! External
external :: &
sgbsv, & ! Single-prec. General Band Solver
dgbsv ! Double-prec. General Band Solver
intrinsic :: eoshift, kind, trim
! Input Variables
character(len=*), intent(in) :: solve_type
integer, intent(in) :: &
nsup, & ! Number of superdiagonals
nsub, & ! Number of subdiagonals
ndim, & ! The order of the LHS Matrix, i.e. the # of linear equations
nrhs ! Number of RHS's to solve for
! Note: matrix lhs is intent(in), not intent(inout)
! as in the subroutine band_solvex( )
real( kind = core_rknd ), dimension(nsup+nsub+1,ndim), intent(in) :: &
lhs ! Left hand side
real( kind = core_rknd ), dimension(ndim,nrhs), intent(inout) :: &
rhs ! Right hand side(s)
! Output Variables
real( kind = core_rknd ), dimension(ndim,nrhs), intent(out) :: solution
integer, intent(out) :: err_code ! Valid calculation?
! Local Variables
! Workspaces
real( kind = core_rknd ), dimension(2*nsub+nsup+1,ndim) :: &
lulhs ! LU Decomposition of the LHS
real( kind = dp ), dimension(2*nsub+nsup+1,ndim) :: &
lulhs_dp
real( kind = dp ), dimension(ndim,nrhs) :: &
rhs_dp
integer, dimension(ndim) :: &
ipivot
integer :: &
info, & ! If this doesn't come back as 0, something went wrong
offset, & ! Loop iterator
imain ! Main diagonal of the matrix
! Copy LHS into Decomposition scratch space
lulhs = 0.0_core_rknd
lulhs(nsub+1:2*nsub+nsup+1, 1:ndim) = lhs(1:nsub+nsup+1, 1:ndim)
!-----------------------------------------------------------------------
! Reorder LU Matrix to use LAPACK band matrix format
! Shift example for lulhs matrix (note the extra bands):
! [ + + + + + + ]
! [ + + + + + + ]
! [ * * lhs(1,1) lhs(1,2) lhs(1,3) lhs(1,4) ] (2)=>
! [ * lhs(2,1) lhs(2,2) lhs(2,3) lhs(2,4) lhs(2,5) ] (1)=>
! [ lhs(3,1) lhs(3,2) lhs(3,3) lhs(3,4) lhs(3,5) lhs(3,6) ]
! <=(1) [ lhs(4,2) lhs(4,3) lhs(4,4) lhs(4,5) lhs(4,6) * ]
! <=(2) [ lhs(5,3) lhs(5,4) lhs(5,5) lhs(5,6) * * ]
! [ + + + + + + ]
! [ + + + + + + ]
! The '*' indicates unreferenced elements.
! The '+' indicates an element overwritten during decomposition.
! For additional bands above and below the main diagonal, the
! shifts to the left or right increases by the distance from the
! main diagonal of the matrix.
!-----------------------------------------------------------------------
! Reorder lulhs, omitting the additional 2*nsub bands
! that are used for the LU decomposition of the matrix.
imain = nsub + nsup + 1
! For the offset, (+) is left, and (-) is right
! Sub diagonals
do offset = 1, nsub, 1
lulhs(imain+offset, 1:ndim) &
= eoshift( lulhs(imain+offset, 1:ndim), offset )
end do
! Super diagonals
do offset = 1, nsup, 1
lulhs(imain-offset, 1:ndim) &
= eoshift( lulhs(imain-offset, 1:ndim), -offset )
end do
!-----------------------------------------------------------------------
! *** LAPACK routine ***
! SUBROUTINE DGBSV( N, KL, KU, NRHS, AB, LDAB, IPIV, B, LDB, INFO )
!-----------------------------------------------------------------------
if ( kind( lhs(1,1) ) == dp ) then
call dgbsv( ndim, nsub, nsup, nrhs, lulhs, nsub*2+nsup+1, &
ipivot, rhs, ndim, info )
else if ( kind( lhs(1,1) ) == sp ) then
call sgbsv( ndim, nsub, nsup, nrhs, lulhs, nsub*2+nsup+1, &
ipivot, rhs, ndim, info )
else
!stop "band_solve: Cannot resolve the precision of real datatype"
! One implication of this is that CLUBB cannot be used with quad
! precision variables without a quad precision band diagonal solver
! Eric Raut Aug 2013: force double precision
lulhs_dp = real( lulhs, kind=dp )
rhs_dp = real( rhs, kind=dp )
call dgbsv( ndim, nsub, nsup, nrhs, lulhs_dp, nsub*2+nsup+1, &
ipivot, rhs_dp, ndim, info )
rhs = real( rhs_dp, kind=core_rknd )
end if
select case( info )
case( :-1 )
write(fstderr,*) trim( solve_type )// &
" illegal value for argument ", -info
err_code = clubb_bad_lapack_arg
solution = -999._core_rknd
case( 0 )
! Success!
if ( lapack_isnan( ndim, nrhs, rhs ) ) then
err_code = clubb_var_equals_NaN
else
err_code = clubb_no_error
end if
solution = rhs
case( 1: )
write(fstderr,*) trim( solve_type )//" band solver: singular matrix"
err_code = clubb_singular_matrix
solution = -999._core_rknd
end select
return
end subroutine band_solve
!-----------------------------------------------------------------------
logical function lapack_isnan( ndim, nrhs, variable )
! Description:
! Check for NaN values in a variable using the LAPACK subroutines
! References:
!
!
!-----------------------------------------------------------------------
use clubb_precision, only: &
core_rknd ! Variable(s)
implicit none
#ifdef NO_LAPACK_ISNAN /* Used for older LAPACK libraries that don't have sisnan/disnan */
intrinsic :: any
integer, intent(in) :: &
ndim, & ! Size of variable
nrhs ! Number of right hand sides
real( kind = core_rknd ), dimension(ndim,nrhs), intent(in) :: &
variable ! Variable to check
lapack_isnan = any( variable(:,1:nrhs) /= variable(:,1:nrhs) )
#else
logical, external :: sisnan, disnan
integer, intent(in) :: &
ndim, & ! Size of variable
nrhs ! Number of right hand sides
real( kind = core_rknd ), dimension(ndim,nrhs), intent(in) :: &
variable ! Variable to check
integer :: k, j
! ---- Begin Code ----
lapack_isnan = .false.
if ( kind( variable ) == dp ) then
do k = 1, ndim
do j = 1, nrhs
lapack_isnan = disnan( variable(k,j) )
if ( lapack_isnan ) exit
end do
if ( lapack_isnan ) exit
end do
else if ( kind( variable ) == sp ) then
do k = 1, ndim
do j = 1, nrhs
lapack_isnan = sisnan( variable(k,j) )
if ( lapack_isnan ) exit
end do
if ( lapack_isnan ) exit
end do
else
stop "lapack_isnan: Cannot resolve the precision of real datatype"
end if
#endif /* NO_LAPACK_ISNAN */
return
end function lapack_isnan
end module lapack_wrap