subroutine nmmatrix(zb      ,zcr1    ,bm1     ,bmi     )
!-----------------------------------------------------------------------
!
! Purpose:
! Determine eigenvalues and left/right eigenvectors of the hydrostatic
! matrix.  Results used to solve for two-time-level SLD, semi-implicit
! Coriolis term
!
! Author:  J. Olson
!
!-----------------------------------------------------------------------
!
! $Id$
! $Author$
!
!-----------------------------------------------------------------------

  use shr_kind_mod, only: r8 => shr_kind_r8
  use pmgrid
  use pspect
  use sgexx,        only: sgeev
  use cam_abortutils,   only: endrun
  use cam_logfile,  only: iulog

  implicit none

!------------------------------Parameters-------------------------------
!
  integer, parameter :: lwork = 100*plev    ! work array dimension
!
!------------------------------Arguments--------------------------------
!
  real(r8), intent(in)   :: zb  (plev,plev) ! semi-implicit matrix in d equation
  real(r8), intent(out)  :: zcr1(plev)      ! eigenvalues (real)
  real(r8), intent(out)  :: bm1 (plev,plev) ! transpose of right eigenvector (normalized)
  real(r8), intent(out)  :: bmi (plev,plev) ! transpose of inverse of bm1
!                                           ! ( = left eigenvectors --  normalized)
!
!---------------------------Local variables-----------------------------
!
  integer  k                    ! index
  integer  kk                   ! index
  integer  kkk                  ! index
  integer  istat                ! status of SGEEV call
  real(r8) eps                  ! used for epsilon tests
  real(r8) sum                  ! tmp
  real(r8) fmax                 ! tmp

  real(r8) x      (plev,plev)   ! local copy of "zb"
  real(r8) bmlft  (plev,plev)   ! left  eigenvector
  real(r8) bmrgt  (plev,plev)   ! right eigenvector
  real(r8) zci    (plev)        ! eigenvalues (complex)
  real(r8) work   (lwork)       ! work array
  real(r8) diag                 ! tmp

  logical  lcomp                ! error flag
  logical  lneg                 ! error flag
  logical  lsame                ! error flag
  logical  lnonzero             ! error flag
  logical  flag(plev)           ! flag used in sorting eigenvalues/vectors
!
!-----------------------------------------------------------------------
!
! Matrix "zb" is stored 1st index = column; 2nd index = row of matrix.
! Transpose "zb" and store in work array before calling library routine
!
  do k = 1,plev
     do kk = 1,plev
        x(k,kk) = zb(kk,k)
     end do
  end do
!
! Determine eigenvalues and left/right eigenvectors of "zb"
!
  call sgeev('v'     ,'v'     ,plev    ,x       ,plev    , &
             zcr1    ,zci     ,bmlft   ,plev    ,bmrgt   , &
             plev    ,work    ,lwork   ,istat   )
!
  if (istat .ne. 0) then
     write(iulog,*) 'ERROR in NMMATRIX:  SGEEV returned "istat" = ',istat
     call endrun ()
  endif
  if (int(work(1)) .gt. lwork) then
     write(iulog,*) 'ERROR in NMMATRIX: dimension of work array must be at least', int(work(1))
     call endrun ()
  endif
!
! Find "eps" based upon the real eigenvalue with the largest magnitude
!
  fmax = -1.e36_r8
  do kk = 1,plev
     if(zcr1(kk) .gt. fmax) fmax = zcr1(kk)
  end do
  eps = fmax*epsilon(fmax)*10._r8
!
! Test that all eigenvalues are positive, real, and distinct
!
  lcomp    = .false.
  lneg     = .false.
  lsame    = .false.
  do k = 1,plev
     if(    zcr1(k)  .lt.  0._r8) lneg  = .true.
     if(abs(zci(k  )) .gt. eps) lcomp = .true.
     do kk = k+1,plev
        if(abs( (zcr1(k) - zcr1(kk)) ) .lt. eps) lsame = .true.
     end do
  end do
  if (lneg) then
     call endrun ('ERROR in NMMATRIX: negative eigenvalue(s)')
  endif
  if (lcomp) then
     call endrun ('ERROR in NMMATRIX: complex eigenvalue(s)')
  endif
  if (lsame) then
     call endrun ('ERROR in NMMATRIX: non-distinct eigenvalue(s)')
  endif
!
! Re-order the eigenvalues (and associated eigenvectors) in descending
! order.  Use "zci", "bmi", and "bm1" as temporary arrays.
!
  do k = 1,plev
     flag(k) = .true.
  end do
  do k = 1,plev
     fmax = -1.e35_r8
     do kk = 1,plev
        if(flag(kk) .and. zcr1(kk) .gt. fmax) then
           kkk = kk
           fmax = zcr1(kk)
        endif
     end do
     flag(kkk) = .false.
     zci(k) = zcr1(kkk)
     do kk = 1,plev
        bmi(kk,k) = bmlft(kk,kkk)
        bm1(kk,k) = bmrgt(kk,kkk)
     end do
  end do
!
! Copy arrays from temporaries back to original arrays
!
  do k = 1,plev
     zcr1(k) = zci(k)
     do kk = 1,plev
        bmlft(kk,k) = bmi(kk,k)
        bmrgt(kk,k) = bm1(kk,k)
     end do
  end do
!
! Make sure the element of largest magnitude within each eigenvector
! is positive (in this case, we will choose the LEFT eigenvector).
! Adjust the signs of all other elements of the left AND associated
! RIGHT eigenvector, accordingly.
!
! NOTE:  This is done only to ensure that the signs of all eigenvectors
! will be consistent across all computing platforms.  This step is
! not necessary except that it makes debugging the "vertical normal
! mode" code MUCH easier.
!
  do k = 1,plev
     fmax = -1.e35_r8
     do kk = 1,plev
        if(abs(bmlft(kk,k)) .gt. fmax) then
           kkk = kk
           fmax = abs(bmlft(kk,k))
        endif
     end do
     if(bmlft(kkk,k) .lt. 0._r8) then
        do kk = 1,plev
           bmlft(kk,k) = -bmlft(kk,k)
           bmrgt(kk,k) = -bmrgt(kk,k)
        end do
     endif
  end do
!
! Normalize "bm1" and determine its inverse (and confirm).
!
! NOTE:  both "bm1" and "bmi" are stored in transpose form (1st index =
! column; 2nd index = row of matrix) for later use in coding vector
! inner products using these matrices.
!
  lnonzero = .false.
  do k = 1,plev
     sum = 0._r8
     do kk = 1,plev
        sum = sum + bmlft(kk,k)*bmrgt(kk,k)
     end do
!
     if(sum .lt. 0._r8) then
        sum = sqrt(-sum)
        do kk = 1,plev
           bmi(kk,k) = -bmlft(kk,k)/sum
           bm1(k,kk) =  bmrgt(kk,k)/sum
        end do
     else
        sum = sqrt(sum)
        do kk = 1,plev
           bmi(kk,k) =  bmlft(kk,k)/sum
           bm1(k,kk) =  bmrgt(kk,k)/sum
        end do
     endif
  end do
  do k = 1,plev
     do kk = 1,plev
        diag = 0._r8
        do kkk = 1,plev
           diag = diag + bmi(kkk,kk)*bm1(k,kkk)
        end do
        if(kk .ne. k) then
           if(diag .gt. eps) lnonzero = .true.
        endif
     end do
  end do
  if (lnonzero) then
     call endrun ('NMMATRIX: Y(T)X has non-zero off-diagonal elements')
  endif
!
  return
end subroutine nmmatrix
