Skip to content
Snippets Groups Projects
summaSolve.f90 75.18 KiB
! SUMMA - Structure for Unifying Multiple Modeling Alternatives
! Copyright (C) 2014-2020 NCAR/RAL; University of Saskatchewan; University of Washington
!
! This file is part of SUMMA
!
! For more information see: http://www.ral.ucar.edu/projects/summa
!
! This program is free software: you can redistribute it and/or modify
! it under the terms of the GNU General Public License as published by
! the Free Software Foundation, either version 3 of the License, or
! (at your option) any later version.
!
! This program is distributed in the hope that it will be useful,
! but WITHOUT ANY WARRANTY; without even the implied warranty of
! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
! GNU General Public License for more details.
!
! You should have received a copy of the GNU General Public License
! along with this program.  If not, see <http://www.gnu.org/licenses/>.

module summaSolve_module

! data types
USE nrtype

! constants
USE multiconst,only:Tfreeze         ! freezing point of pure water (K)
USE multiconst,only:iden_water      ! intrinsic density of liquid water (kg m-3)

! access the global print flag
USE globalData,only:globalPrintFlag

! access missing values
USE globalData,only:integerMissing  ! missing integer
USE globalData,only:realMissing     ! missing double precision number
USE globalData,only:quadMissing     ! missing quadruple precision number

! access named variables to describe the form and structure of the matrices used in the numerical solver
USE globalData,only: ku             ! number of super-diagonal bands
USE globalData,only: kl             ! number of sub-diagonal bands
USE globalData,only: nBands         ! length of the leading dimension of the band diagonal matrix
USE globalData,only: ixFullMatrix   ! named variable for the full Jacobian matrix
USE globalData,only: ixBandMatrix   ! named variable for the band diagonal matrix
USE globalData,only: iJac1          ! first layer of the Jacobian to print
USE globalData,only: iJac2          ! last layer of the Jacobian to print

! named variables to describe the state variable type
USE globalData,only:iname_nrgCanair ! named variable defining the energy of the canopy air space
USE globalData,only:iname_nrgCanopy ! named variable defining the energy of the vegetation canopy
USE globalData,only:iname_watCanopy ! named variable defining the mass of water on the vegetation canopy
USE globalData,only:iname_nrgLayer  ! named variable defining the energy state variable for snow+soil layers
USE globalData,only:iname_watLayer  ! named variable defining the total water state variable for snow+soil layers
USE globalData,only:iname_liqLayer  ! named variable defining the liquid  water state variable for snow+soil layers
USE globalData,only:iname_matLayer  ! named variable defining the matric head state variable for soil layers
USE globalData,only:iname_lmpLayer  ! named variable defining the liquid matric potential state variable for soil layers

! indices of elements of data structure
USE var_lookup,only:iLookFLUX       ! named variables for structure elements
USE var_lookup,only:iLookPROG       ! named variables for structure elements
USE var_lookup,only:iLookPARAM      ! named variables for structure elements
USE var_lookup,only:iLookINDEX      ! named variables for structure elements
USE var_lookup,only:iLookDECISIONS  ! named variables for elements of the decision structure

! provide access to the derived types to define the data structures
USE data_types,only:&
                    var_i,        & ! data vector (i4b)
                    var_d,        & ! data vector (dp)
                    var_ilength,  & ! data vector with variable length dimension (i4b)
                    var_dlength,  & ! data vector with variable length dimension (dp)
                    model_options   ! defines the model decisions

! look-up values for the choice of groundwater parameterization
USE mDecisions_module,only:       &
                    qbaseTopmodel,& ! TOPMODEL-ish baseflow parameterization
                    bigBucket,    & ! a big bucket (lumped aquifer model)
                    noExplicit      ! no explicit groundwater parameterization

implicit none
private
public::summaSolve
contains

 ! *********************************************************************************************************
 ! public subroutine summaSolve: calculate the iteration increment, evaluate the new state, and refine if necessary
 ! *********************************************************************************************************
 subroutine summaSolve(&
                       ! input: model control
                       dt,                      & ! intent(in):    length of the time step (seconds)
                       iter,                    & ! intent(in):    iteration index
                       nSnow,                   & ! intent(in):    number of snow layers
                       nSoil,                   & ! intent(in):    number of soil layers
                       nLayers,                 & ! intent(in):    total number of layers
                       nLeadDim,                & ! intent(in):    length of the leading dimension of the Jacobian matrix (either nBands or nState)
                       nState,                  & ! intent(in):    total number of state variables
                       ixMatrix,                & ! intent(in):    type of matrix (full or band diagonal)
                       firstSubStep,            & ! intent(in):    flag to indicate if we are processing the first sub-step
                       firstFluxCall,           & ! intent(inout): flag to indicate if we are processing the first flux call
                       computeVegFlux,          & ! intent(in):    flag to indicate if we need to compute fluxes over vegetation
                       scalarSolution,          & ! intent(in):    flag to indicate the scalar solution
                       ! input: state vectors
                       stateVecTrial,           & ! intent(in):    trial state vector
                       xMin,xMax,               & ! intent(inout): brackets of the root
                       fScale,                  & ! intent(in):    function scaling vector
                       xScale,                  & ! intent(in):    "variable" scaling vector, i.e., for state variables
                       rVec,                    & ! intent(in):    residual vector
                       sMul,                    & ! intent(in):    state vector multiplier (used in the residual calculations)
                       dMat,                    & ! intent(inout): diagonal matrix (excludes flux derivatives)
                       fOld,                    & ! intent(in):    old function evaluation
                       ! input: data structures
                       model_decisions,         & ! intent(in):    model decisions
                       type_data,               & ! intent(in):    type of vegetation and soil
                       attr_data,               & ! intent(in):    spatial attributes
                       mpar_data,               & ! intent(in):    model parameters
                       forc_data,               & ! intent(in):    model forcing data
                       bvar_data,               & ! intent(in):    average model variables for the entire basin
                       prog_data,               & ! intent(in):    model prognostic variables for a local HRU
                       ! input-output: data structures
                       indx_data,               & ! intent(inout): index data
                       diag_data,               & ! intent(inout): model diagnostic variables for a local HRU
                       flux_data,               & ! intent(inout): model fluxes for a local HRU
                       deriv_data,              & ! intent(inout): derivatives in model fluxes w.r.t. relevant state variables
                       ! input-output: baseflow
                       ixSaturation,            & ! intent(inout): index of the lowest saturated layer (NOTE: only computed on the first iteration)
                       dBaseflow_dMatric,       & ! intent(inout): derivative in baseflow w.r.t. matric head (s-1)
                       ! output
                       stateVecNew,             & ! intent(out):   new state vector
                       fluxVecNew,              & ! intent(out):   new flux vector
                       resSinkNew,              & ! intent(out):   additional (sink) terms on the RHS of the state equation
                       resVecNew,               & ! intent(out):   new residual vector
                       fNew,                    & ! intent(out):   new function evaluation
                       converged,               & ! intent(out):   convergence flag
                       err,message)               ! intent(out):   error control
 USE computJacob_module, only: computJacob
 USE matrixOper_module,  only: lapackSolv
 USE matrixOper_module,  only: scaleMatrices
 implicit none
 ! --------------------------------------------------------------------------------------------------------------------------------
 ! input: model control
 real(dp),intent(in)             :: dt                       ! length of the time step (seconds)
 integer(i4b),intent(in)         :: iter                     ! interation index
 integer(i4b),intent(in)         :: nSnow                    ! number of snow layers
 integer(i4b),intent(in)         :: nSoil                    ! number of soil layers
 integer(i4b),intent(in)         :: nLayers                  ! total number of layers
 integer(i4b),intent(in)         :: nLeadDim                 ! length of the leading dimension of the Jacobian matrix (nBands or nState)
 integer(i4b),intent(in)         :: nState                   ! total number of state variables
 integer(i4b),intent(in)         :: ixMatrix                 ! type of matrix (full or band diagonal)
 logical(lgt),intent(in)         :: firstSubStep             ! flag to indicate if we are processing the first sub-step
 logical(lgt),intent(inout)      :: firstFluxCall            ! flag to indicate if we are processing the first flux call
 logical(lgt),intent(in)         :: computeVegFlux           ! flag to indicate if computing fluxes over vegetation
 logical(lgt),intent(in)         :: scalarSolution           ! flag to denote if implementing the scalar solution
 ! input: state vectors
 real(dp),intent(in)             :: stateVecTrial(:)         ! trial state vector
 real(dp),intent(inout)          :: xMin,xMax                ! brackets of the root
 real(dp),intent(in)             :: fScale(:)                ! function scaling vector
 real(dp),intent(in)             :: xScale(:)                ! "variable" scaling vector, i.e., for state variables
 real(qp),intent(in)             :: rVec(:)   ! NOTE: qp     ! residual vector
 real(qp),intent(in)             :: sMul(:)   ! NOTE: qp     ! state vector multiplier (used in the residual calculations)
 real(dp),intent(inout)          :: dMat(:)                  ! diagonal matrix (excludes flux derivatives)
 real(dp),intent(in)             :: fOld                     ! old function evaluation
 ! input: data structures
 type(model_options),intent(in)  :: model_decisions(:)       ! model decisions
 type(var_i),        intent(in)  :: type_data                ! type of vegetation and soil
 type(var_d),        intent(in)  :: attr_data                ! spatial attributes
 type(var_dlength),  intent(in)  :: mpar_data                ! model parameters
 type(var_d),        intent(in)  :: forc_data                ! model forcing data
 type(var_dlength),  intent(in)  :: bvar_data                ! model variables for the local basin
 type(var_dlength),  intent(in)  :: prog_data                ! prognostic variables for a local HRU
 ! output: data structures
 type(var_ilength),intent(inout) :: indx_data                ! indices defining model states and layers
 type(var_dlength),intent(inout) :: diag_data                ! diagnostic variables for a local HRU
 type(var_dlength),intent(inout) :: flux_data                ! model fluxes for a local HRU
 type(var_dlength),intent(inout) :: deriv_data               ! derivatives in model fluxes w.r.t. relevant state variables
 ! input-output: baseflow
 integer(i4b),intent(inout)      :: ixSaturation             ! index of the lowest saturated layer (NOTE: only computed on the first iteration)
 real(dp),intent(inout)          :: dBaseflow_dMatric(:,:)   ! derivative in baseflow w.r.t. matric head (s-1)
 ! output: flux and residual vectors
 real(dp),intent(out)            :: stateVecNew(:)           ! new state vector
 real(dp),intent(out)            :: fluxVecNew(:)            ! new flux vector
 real(dp),intent(out)            :: resSinkNew(:)            ! sink terms on the RHS of the flux equation
 real(qp),intent(out)            :: resVecNew(:) ! NOTE: qp  ! new residual vector
 real(dp),intent(out)            :: fNew                     ! new function evaluation
 logical(lgt),intent(out)        :: converged                ! convergence flag
 ! output: error control
 integer(i4b),intent(out)        :: err                      ! error code
 character(*),intent(out)        :: message                  ! error message
 ! --------------------------------------------------------------------------------------------------------------------------------
 ! local variables
 ! --------------------------------------------------------------------------------------------------------------------------------
 ! Jacobian matrix
 logical(lgt),parameter          :: doNumJacobian=.false.    ! flag to compute the numerical Jacobian matrix
 logical(lgt),parameter          :: testBandDiagonal=.false. ! flag to test the band diagonal Jacobian matrix
 real(dp)                        :: nJac(nState,nState)      ! numerical Jacobian matrix
 real(dp)                        :: aJac(nLeadDim,nState)      ! Jacobian matrix
 real(dp)                        :: aJacScaled(nLeadDim,nState)      ! Jacobian matrix (scaled)
 real(dp)                        :: aJacScaledTemp(nLeadDim,nState)  ! Jacobian matrix (scaled) -- temporary copy since decomposed in lapack
 ! solution/step vectors
 real(dp),dimension(nState)      :: rVecScaled               ! residual vector (scaled)
 real(dp),dimension(nState)      :: newtStepScaled           ! full newton step (scaled)
 ! step size refinement
 logical(lgt)                    :: doRefine                 ! flag for step refinement
 integer(i4b),parameter          :: ixLineSearch=1001        ! step refinement = line search
 integer(i4b),parameter          :: ixTrustRegion=1002       ! step refinement = trust region
 integer(i4b),parameter          :: ixStepRefinement=ixLineSearch   ! decision for the numerical solution
 ! general
 integer(i4b)                    :: mSoil                    ! number of soil layers in solution vector
 integer(i4b)                    :: iLayer                   ! row index
 integer(i4b)                    :: jLayer                   ! column index
 logical(lgt)                    :: globalPrintFlagInit      ! initial global print flag
 character(LEN=256)              :: cmessage                 ! error message of downwind routine
 ! --------------------------------------------------------------------------------------------------------------------------------
 ! associations to information in data structures
 associate(ixGroundwater => model_decisions(iLookDECISIONS%groundwatr)%iDecision)  ! intent(in): [i4b] groundwater parameterization
 ! --------------------------------------------------------------------------------------------------------------------------------
 ! initialize error control
 err=0; message='summaSolve/'

 ! get the number of soil layers in the solution vector
 mSoil = size(indx_data%var(iLookINDEX%ixMatOnly)%dat)

 ! initialize the global print flag
 globalPrintFlagInit=globalPrintFlag

 ! -----
 ! * compute the Jacobian matrix...
 ! --------------------------------

 ! compute the analytical Jacobian matrix
 ! NOTE: The derivatives were computed in the previous call to computFlux
 !       This occurred either at the call to eval8summa at the start of systemSolv
 !        or in the call to eval8summa in the previous iteration (within lineSearchRefinement or trustRegionRefinement)
 call computJacob(&
                  ! input: model control
                  dt,                             & ! intent(in):    length of the time step (seconds)
                  nSnow,                          & ! intent(in):    number of snow layers
                  nSoil,                          & ! intent(in):    number of soil layers
                  nLayers,                        & ! intent(in):    total number of layers
                  computeVegFlux,                 & ! intent(in):    flag to indicate if we need to compute fluxes over vegetation
                  (ixGroundwater==qbaseTopmodel), & ! intent(in):    flag to indicate if we need to compute baseflow
                  ixMatrix,                       & ! intent(in):    form of the Jacobian matrix
                  ! input: data structures
                  indx_data,                      & ! intent(in):    index data
                  prog_data,                      & ! intent(in):    model prognostic variables for a local HRU
                  diag_data,                      & ! intent(in):    model diagnostic variables for a local HRU
                  deriv_data,                     & ! intent(in):    derivatives in model fluxes w.r.t. relevant state variables
                  dBaseflow_dMatric,              & ! intent(in):    derivative in baseflow w.r.t. matric head (s-1)
                  ! input-output: Jacobian and its diagonal
                  dMat,                           & ! intent(inout): diagonal of the Jacobian matrix
                  aJac,                           & ! intent(out):   Jacobian matrix
                  ! output: error control
                  err,cmessage)                     ! intent(out):   error code and error message
 if(err/=0)then; message=trim(message)//trim(cmessage); return; end if  ! (check for errors)

 ! compute the numerical Jacobian matrix
 if(doNumJacobian)then
  globalPrintFlag=.false.
  call numJacobian(stateVecTrial,dMat,nJac,err,cmessage)
  if(err/=0)then; message=trim(message)//trim(cmessage); return; end if  ! (check for errors)
  globalPrintFlag=globalPrintFlagInit
 endif

 ! test the band diagonal matrix
 if(testBandDiagonal)then
  call testBandMat(check=.true.,err=err,message=cmessage)
  if(err/=0)then; message=trim(message)//trim(cmessage); return; end if  ! (check for errors)
 endif

 ! -----
 ! * solve linear system...
 ! ------------------------

 ! scale the residual vector
 rVecScaled(1:nState) = fScale(:)*real(rVec(:), dp)   ! NOTE: residual vector is in quadruple precision

 ! scale matrices
 call scaleMatrices(ixMatrix,nState,aJac,fScale,xScale,aJacScaled,err,cmessage)
 if(err/=0)then; message=trim(message)//trim(cmessage); return; end if  ! (check for errors)

 if(globalPrintFlag .and. ixMatrix==ixBandMatrix)then
  print*, '** SCALED banded analytical Jacobian:'
  write(*,'(a4,1x,100(i17,1x))') 'xCol', (iLayer, iLayer=iJac1,iJac2)
  do iLayer=kl+1,nBands
   write(*,'(i4,1x,100(e17.10,1x))') iLayer, (aJacScaled(iLayer,jLayer),jLayer=min(iJac1,nState),min(iJac2,nState))
  end do
 end if

 ! copy the scaled matrix, since it is decomposed in lapackSolv
 aJacScaledTemp = aJacScaled

 ! compute the newton step: use the lapack routines to solve the linear system A.X=B
 call lapackSolv(ixMatrix,nState,aJacScaledTemp,-rVecScaled,newtStepScaled,err,cmessage)
 if(err/=0)then; message=trim(message)//trim(cmessage); return; end if  ! (check for errors)

 if(globalPrintFlag)&
 write(*,'(a,1x,10(e17.10,1x))') 'newtStepScaled = ', newtStepScaled(min(iJac1,nState):min(iJac2,nState))
 !print*, 'PAUSE'; read(*,*)

 ! -----
 ! * update, evaluate, and refine the state vector...
 ! --------------------------------------------------

 ! initialize the flag for step refinement
 doRefine=.true.

 ! * case 1: state vector
 ! compute the flux vector and the residual, and (if necessary) refine the iteration increment
 ! NOTE: in 99.9% of cases newtStep will be used (no refinement)
 if(size(stateVecTrial)>1)then

  ! try to backtrack
  select case(ixStepRefinement)
   case(ixLineSearch);  call lineSearchRefinement( doRefine,stateVecTrial,newtStepScaled,aJacScaled,rVecScaled,fOld,stateVecNew,fluxVecNew,resVecNew,fNew,converged,err,cmessage)
   case(ixTrustRegion); call trustRegionRefinement(doRefine,stateVecTrial,newtStepScaled,aJacScaled,rVecScaled,fOld,stateVecNew,fluxVecNew,resVecNew,fNew,converged,err,cmessage)
   case default; err=20; message=trim(message)//'unable to identify numerical solution'; return
  end select

  ! check warnings: negative error code = warning; in this case back-tracked to the original value
  ! NOTE: Accept the full newton step if back-tracked to the original value
  if(err<0)then
   doRefine=.false.;    call lineSearchRefinement( doRefine,stateVecTrial,newtStepScaled,aJacScaled,rVecScaled,fOld,stateVecNew,fluxVecNew,resVecNew,fNew,converged,err,cmessage)
  end if

 ! * case 2: scalar
 else
  call safeRootfinder(stateVecTrial,rVecScaled,newtStepScaled,stateVecNew,fluxVecNew,resVecNew,fNew,converged,err,cmessage)
  if(err/=0)then; message=trim(message)//trim(cmessage); return; end if  ! (check for errors)
 endif

 ! check errors
 if(err/=0)then; message=trim(message)//trim(cmessage); return; end if  ! (check for errors)

 ! end association to info in data structures
 end associate

 contains

  ! *********************************************************************************************************
  ! * internal subroutine lineSearchRefinement: refine the iteration increment using line searches
  ! *********************************************************************************************************
  subroutine lineSearchRefinement(doLineSearch,stateVecTrial,newtStepScaled,aJacScaled,rVecScaled,fOld,stateVecNew,fluxVecNew,resVecNew,fNew,converged,err,message)
  ! provide access to the matrix routines
  USE matrixOper_module, only: computeGradient
  implicit none
  ! input
  logical(lgt),intent(in)        :: doLineSearch             ! flag to do the line search
  real(dp),intent(in)            :: stateVecTrial(:)         ! trial state vector
  real(dp),intent(in)            :: newtStepScaled(:)        ! scaled newton step
  real(dp),intent(in)            :: aJacScaled(:,:)          ! scaled jacobian matrix
  real(dp),intent(in)            :: rVecScaled(:)            ! scaled residual vector
  real(dp),intent(in)            :: fOld                     ! old function value
  ! output
  real(dp),intent(out)           :: stateVecNew(:)           ! new state vector
  real(dp),intent(out)           :: fluxVecNew(:)            ! new flux vector
  real(qp),intent(out)           :: resVecNew(:) ! NOTE: qp  ! new residual vector
  real(dp),intent(out)           :: fNew                     ! new function evaluation
  logical(lgt),intent(out)       :: converged                ! convergence flag
  integer(i4b),intent(out)       :: err                      ! error code
  character(*),intent(out)       :: message                  ! error message
  ! --------------------------------------------------------------------------------------------------------
  ! local
  character(len=256)             :: cmessage                 ! error message of downwind routine
  real(dp)                       :: gradScaled(nState)       ! scaled gradient
  real(dp)                       :: xInc(nState)             ! iteration increment (re-scaled to original units of the state vector)
  logical(lgt)                   :: feasible                 ! flag to denote the feasibility of the solution
  integer(i4b)                   :: iLine                    ! line search index
  integer(i4b),parameter         :: maxLineSearch=5          ! maximum number of backtracks
  real(dp),parameter             :: alpha=1.e-4_dp           ! check on gradient
  real(dp)                       :: xLambda                  ! backtrack magnitude
  real(dp)                       :: xLambdaTemp              ! temporary backtrack magnitude
  real(dp)                       :: slopeInit                ! initial slope
  real(dp)                       :: rhs1,rhs2                ! rhs used to compute the cubic
  real(dp)                       :: aCoef,bCoef              ! coefficients in the cubic
  real(dp)                       :: disc                     ! temporary variable used in cubic
  real(dp)                       :: xLambdaPrev              ! previous lambda value (used in the cubic)
  real(dp)                       :: fPrev                    ! previous function evaluation (used in the cubic)
  ! --------------------------------------------------------------------------------------------------------
  ! initialize error control
  err=0; message='lineSearchRefinement/'

  ! check the need to compute the line search
  if(doLineSearch)then

   ! compute the gradient of the function vector
   call computeGradient(ixMatrix,nState,aJacScaled,rVecScaled,gradScaled,err,cmessage)
   if(err/=0)then; message=trim(message)//trim(cmessage); return; end if  ! (check for errors)

   ! compute the initial slope
   slopeInit = dot_product(gradScaled,newtStepScaled)

  end if  ! if computing the line search

  ! initialize lambda
  xLambda=1._dp

  ! ***** LINE SEARCH LOOP...
  lineSearch: do iLine=1,maxLineSearch  ! try to refine the function by shrinking the step size

   ! back-track along the search direction
   ! NOTE: start with back-tracking the scaled step
   xInc(:) = xLambda*newtStepScaled(:)

   ! re-scale the iteration increment
   xInc(:) = xInc(:)*xScale(:)

   ! if enthalpy, then need to convert the iteration increment to temperature
   !if(nrgFormulation==ix_enthalpy) xInc(ixNrgOnly) = xInc(ixNrgOnly)/dMat(ixNrgOnly)

   ! impose solution constraints
   ! NOTE: we may not need to do this (or at least, do ALL of this), as we can probably rely on the line search here
   !  (especially the feasibility check)
   call imposeConstraints(stateVecTrial,xInc,err,cmessage)
   if(err/=0)then; message=trim(message)//trim(cmessage); return; end if  ! (check for errors)

   ! compute the iteration increment
   stateVecNew = stateVecTrial + xInc

   ! compute the residual vector and function
   ! NOTE: This calls eval8summa in an internal subroutine
   !       The internal sub routine has access to all data
   !       Hence, we only need to include the variables of interest in lineSearch
   call eval8summa_wrapper(stateVecNew,fluxVecNew,resVecNew,fNew,feasible,err,cmessage)
   if(err/=0)then; message=trim(message)//trim(cmessage); return; end if  ! (check for errors)

   ! check line search
   if(globalPrintFlag)then
    write(*,'(a,1x,i4,1x,e17.10)' ) 'iLine, xLambda                 = ', iLine, xLambda
    write(*,'(a,1x,10(e17.10,1x))') 'fOld,fNew                      = ', fOld,fNew
    write(*,'(a,1x,10(e17.10,1x))') 'fold + alpha*slopeInit*xLambda = ', fold + alpha*slopeInit*xLambda
    write(*,'(a,1x,10(e17.10,1x))') 'resVecNew                      = ', resVecNew(min(iJac1,nState):min(iJac2,nState))
    write(*,'(a,1x,10(e17.10,1x))') 'xInc                           = ', xInc(min(iJac1,nState):min(iJac2,nState))
   end if

   ! check feasibility
   if(.not.feasible) cycle

   ! check convergence
   ! NOTE: some efficiency gains possible by scaling the full newton step outside the line search loop
   converged = checkConv(resVecNew,newtStepScaled*xScale,stateVecNew)
   if(converged) return

   ! early return if not computing the line search
   if(.not.doLineSearch) return

   ! check if the function is accepted
   if(fNew < fold + alpha*slopeInit*xLambda) return

   ! ***
   ! *** IF GET TO HERE WE BACKTRACK
   !      --> all remaining code simply computes the restricted step multiplier (xLambda)

   ! first backtrack: use quadratic
   if(iLine==1)then
    xLambdaTemp = -slopeInit / (2._dp*(fNew - fOld - slopeInit) )
    if(xLambdaTemp > 0.5_dp*xLambda) xLambdaTemp = 0.5_dp*xLambda

   ! subsequent backtracks: use cubic
   else

    ! check that we did not back-track all the way back to the original value
    if(iLine==maxLineSearch)then
     message=trim(message)//'backtracked all the way back to the original value'
     err=-20; return
    end if

    ! define rhs
    rhs1 = fNew - fOld - xLambda*slopeInit
    rhs2 = fPrev - fOld - xLambdaPrev*slopeInit

    ! define coefficients
    aCoef = (rhs1/(xLambda*xLambda) - rhs2/(xLambdaPrev*xLambdaPrev))/(xLambda - xLambdaPrev)
    bCoef = (-xLambdaPrev*rhs1/(xLambda*xLambda) + xLambda*rhs2/(xLambdaPrev*xLambdaPrev)) / (xLambda - xLambdaPrev)

    ! check if a quadratic
    if(aCoef==0._dp)then
     xLambdaTemp = -slopeInit/(2._dp*bCoef)

    ! calculate cubic
    else
     disc = bCoef*bCoef - 3._dp*aCoef*slopeInit
     if(disc < 0._dp)then
      xLambdaTemp = 0.5_dp*xLambda
     else
      xLambdaTemp = (-bCoef + sqrt(disc))/(3._dp*aCoef)
     end if
    end if  ! calculating cubic

    ! constrain to <= 0.5*xLambda
    if(xLambdaTemp > 0.5_dp*xLambda) xLambdaTemp=0.5_dp*xLambda

   end if  ! subsequent backtracks
   ! save results
   xLambdaPrev = xLambda
   fPrev = fNew

   ! constrain lambda
   xLambda = max(xLambdaTemp, 0.1_dp*xLambda)

  end do lineSearch  ! backtrack loop

  end subroutine lineSearchRefinement


  ! *********************************************************************************************************
  ! * internal subroutine trustRegionRefinement: refine the iteration increment using trust regions
  ! *********************************************************************************************************
  subroutine trustRegionRefinement(doTrustRefinement,stateVecTrial,newtStepScaled,aJacScaled,rVecScaled,fOld,stateVecNew,fluxVecNew,resVecNew,fNew,converged,err,message)
  ! provide access to the matrix routines
  USE matrixOper_module, only: lapackSolv
  USE matrixOper_module, only: computeGradient
  implicit none
  ! input
  logical(lgt),intent(in)        :: doTrustRefinement        ! flag to refine using trust regions
  real(dp),intent(in)            :: stateVecTrial(:)         ! trial state vector
  real(dp),intent(in)            :: newtStepScaled(:)        ! scaled newton step
  real(dp),intent(in)            :: aJacScaled(:,:)          ! scaled jacobian matrix
  real(dp),intent(in)            :: rVecScaled(:)            ! scaled residual vector
  real(dp),intent(in)            :: fOld                     ! old function value
  ! output
  real(dp),intent(out)           :: stateVecNew(:)           ! new state vector
  real(dp),intent(out)           :: fluxVecNew(:)            ! new flux vector
  real(qp),intent(out)           :: resVecNew(:) ! NOTE: qp  ! new residual vector
  real(dp),intent(out)           :: fNew                     ! new function evaluation
  logical(lgt),intent(out)       :: converged                ! convergence flag
  integer(i4b),intent(out)       :: err                      ! error code
  character(*),intent(out)       :: message                  ! error message
  ! --------------------------------------------------------------------------------------------------------
  ! local variables

  ! .. needed ..


  ! --------------------------------------------------------------------------------------------------------
  err=0; message='trustRegionRefinement/'

  ! check the need to refine the step
  if(doTrustRefinement)then

   ! (check vectors)
   if(size(stateVecTrial)/=nState .or. size(newtStepScaled)/=nState .or. size(rVecScaled)/=nState)then
    message=trim(message)//'unexpected size of input vectors'
    err=20; return
   endif

   ! (check matrix)
   if(size(aJacScaled,1)/=nState .or. size(aJacScaled,2)/=nState)then
    message=trim(message)//'unexpected size of Jacobian matrix'
    err=20; return
   endif

   ! dummy check for the function
   if(fold==realMissing) print*, 'missing'

   ! dummy
   stateVecNew = realMissing
   fluxVecNew  = realMissing
   resVecNew   = quadMissing
   fNew        = realMissing
   converged   = .true.

  endif  ! if doing the trust region refinement

  message=trim(message)//'routine not implemented yet'
  err=20; return



  end subroutine trustRegionRefinement


  ! *********************************************************************************************************
  ! * internal subroutine safeRootfinder: refine the 1-d iteration increment using brackets
  ! *********************************************************************************************************
  subroutine safeRootfinder(stateVecTrial,rVecscaled,newtStepScaled,stateVecNew,fluxVecNew,resVecNew,fNew,converged,err,message)
  USE,intrinsic :: ieee_arithmetic,only:ieee_is_nan          ! IEEE arithmetic (check NaN)
  USE globalData,only:dNaN                                   ! double precision NaN
  implicit none
  ! input
  real(dp),intent(in)            :: stateVecTrial(:)         ! trial state vector
  real(dp),intent(in)            :: rVecScaled(:)            ! scaled residual vector
  real(dp),intent(in)            :: newtStepScaled(:)        ! scaled newton step
  ! output
  real(dp),intent(out)           :: stateVecNew(:)           ! new state vector
  real(dp),intent(out)           :: fluxVecNew(:)            ! new flux vector
  real(qp),intent(out)           :: resVecNew(:) ! NOTE: qp  ! new residual vector
  real(dp),intent(out)           :: fNew                     ! new function evaluation
  logical(lgt),intent(out)       :: converged                ! convergence flag
  integer(i4b),intent(out)       :: err                      ! error code
  character(*),intent(out)       :: message                  ! error message
  ! --------------------------------------------------------------------------------------------------------
  ! local variables
  character(len=256)             :: cmessage                 ! error message of downwind routine
  real(dp),parameter             :: relTolerance=0.005_dp    ! force bi-section if trial is slightly larger than (smaller than) xmin (xmax)
  real(dp)                       :: xTolerance               ! relTolerance*(xmax-xmin)
  real(dp)                       :: xInc(nState)             ! iteration increment (re-scaled to original units of the state vector)
  real(dp)                       :: rVec(nState)             ! residual vector (re-scaled to original units of the state equation)
  logical(lgt)                   :: feasible                 ! feasibility of the solution
  logical(lgt)                   :: doBisection              ! flag to do the bi-section
  logical(lgt)                   :: bracketsDefined          ! flag to define if the brackets are defined
  !integer(i4b)                  :: iCheck                   ! check the model state variables (not used)
  integer(i4b),parameter         :: nCheck=100               ! number of times to check the model state variables
  real(dp),parameter             :: delX=1._dp               ! trial increment
  !real(dp)                      :: xIncrement(nState)       ! trial increment (not used)
  ! --------------------------------------------------------------------------------------------------------
  err=0; message='safeRootfinder/'

  ! check scalar
  if(size(stateVecTrial)/=1 .or. size(rVecScaled)/=1 .or. size(newtStepScaled)/=1)then
   message=trim(message)//'unexpected size of input vectors'
   err=20; return
  endif

  ! initialize brackets to double precision Indian bread
  if(iter==1)then
   xMax = dNaN
   xMin = dNaN
  endif

  ! get the residual vector
  rVec = real(rVecScaled, dp)*fScale

  ! update brackets
  if(rVec(1)<0._dp)then
   xMin = stateVecTrial(1)
  else
   xMax = stateVecTrial(1)
  endif

  ! get the iteration increment
  xInc = newtStepScaled*xScale

  ! *****
  ! * case 1: the iteration increment is the same sign as the residual vector
  if(xInc(1)*rVec(1) > 0._dp)then

   ! get brackets if they do not exist
   if( ieee_is_nan(xMin) .or. ieee_is_nan(xMax) )then
    call getBrackets(stateVecTrial,stateVecNew,xMin,xMax,err,cmessage)
    if(err/=0)then; message=trim(message)//trim(cmessage); return; end if  ! (check for errors)
   endif

   ! use bi-section
   stateVecNew(1) = 0.5_dp*(xMin + xMax)

  ! *****
  ! * case 2: the iteration increment is the correct sign
  else

   ! impose solution constraints
   call imposeConstraints(stateVecTrial,xInc,err,cmessage)
   if(err/=0)then; message=trim(message)//trim(cmessage); return; end if  ! (check for errors)

   ! compute the iteration increment
   stateVecNew = stateVecTrial + xInc

  endif  ! if the iteration increment is the same sign as the residual vector

  ! bi-section
  bracketsDefined = ( .not.ieee_is_nan(xMin) .and. .not.ieee_is_nan(xMax) )  ! check that the brackets are defined
  if(bracketsDefined)then
   xTolerance  = relTolerance*(xMax-xMin)
   doBisection = (stateVecNew(1)<xMin+xTolerance .or. stateVecNew(1)>xMax-xTolerance)
   if(doBisection) stateVecNew(1) = 0.5_dp*(xMin+xMax)
  endif

  ! evaluate summa
  call eval8summa_wrapper(stateVecNew,fluxVecNew,resVecNew,fNew,feasible,err,cmessage)
  if(err/=0)then; message=trim(message)//trim(cmessage); return; end if  ! (check for errors)

  ! check feasibility (should be feasible because of the call to imposeConstraints
  if(.not.feasible)then; err=20; message=trim(message)//'infeasible solution'; return; endif

  ! check convergence
  converged = checkConv(resVecNew,xInc,stateVecNew)

  !write(*,'(a,1x,2(L1,1x),5(e20.8,1x))') 'bracketsDefined, doBisection, xMin, xMax, stateVecTrial, stateVecNew, xInc = ', &
  !                                        bracketsDefined, doBisection, xMin, xMax, stateVecTrial, stateVecNew, xInc
  !print*, 'PAUSE'; read(*,*)

  end subroutine safeRootfinder

  ! *********************************************************************************************************
  ! * internal subroutine getBrackets: get the brackets
  ! *********************************************************************************************************
  subroutine getBrackets(stateVecTrial,stateVecNew,xMin,xMax,err,message)
  USE,intrinsic :: ieee_arithmetic,only:ieee_is_nan          ! IEEE arithmetic (check NaN)
  implicit none
  ! dummies
  real(dp),intent(in)            :: stateVecTrial(:)         ! trial state vector
  real(dp),intent(out)           :: stateVecNew(:)           ! new state vector
  real(dp),intent(out)           :: xMin,xMax                ! constraints
  integer(i4b),intent(inout)     :: err                      ! error code
  character(*),intent(out)       :: message                  ! error message
  ! locals
  integer(i4b)                   :: iCheck                   ! check the model state variables
  integer(i4b),parameter         :: nCheck=100               ! number of times to check the model state variables
  logical(lgt)                   :: feasible                 ! feasibility of the solution
  real(dp),parameter             :: delX=1._dp               ! trial increment
  real(dp)                       :: xIncrement(nState)       ! trial increment
  ! initialize
  err=0; message='getBrackets/'

  ! initialize state vector
  stateVecNew = stateVecTrial

  ! get xIncrement
  xIncrement = -sign((/delX/),rVec)

  ! try the increment a few times
  do iCheck=1,nCheck

   ! impose solution constraints
   call imposeConstraints(stateVecNew,xIncrement,err,cmessage)
   if(err/=0)then; message=trim(message)//trim(cmessage); return; end if  ! (check for errors)

   ! increment state vector
   stateVecNew = stateVecNew + xIncrement

   ! evaluate summa
   call eval8summa_wrapper(stateVecNew,fluxVecNew,resVecNew,fNew,feasible,err,cmessage)
   if(err/=0)then; message=trim(message)//trim(cmessage); return; end if  ! (check for errors)

   ! check that the trial value is feasible (should not happen because of the call to impose constraints)
   if(.not.feasible)then; message=trim(message)//'state vector is not feasible'; err=20; return; endif

   ! update brackets
   if(real(resVecNew(1), dp)<0._dp)then
    xMin = stateVecNew(1)
   else
    xMax = stateVecNew(1)
   endif

   ! print progress
   !print*, 'xMin, xMax, stateVecTrial, stateVecNew, resVecNew, xIncrement = ', &
   !         xMin, xMax, stateVecTrial, stateVecNew, resVecNew, xIncrement

   ! check that the brackets are defined
   if( .not.ieee_is_nan(xMin) .and. .not.ieee_is_nan(xMax) ) exit

   ! check that we found the brackets
   if(iCheck==nCheck)then
    message=trim(message)//'could not fix the problem where residual and iteration increment are of the same sign'
    err=20; return
   endif

  end do  ! multiple checks

  end subroutine getBrackets


  ! *********************************************************************************************************
  ! * internal subroutine numJacobian: compute the numerical Jacobian matrix
  ! *********************************************************************************************************
  subroutine numJacobian(stateVec,dMat,nJac,err,message)
  implicit none
  ! dummies
  real(dp),intent(in)            :: stateVec(:)                ! trial state vector
  real(dp),intent(in)            :: dMat(:)                    ! diagonal matrix
  ! output
  real(dp),intent(out)           :: nJac(:,:)                  ! numerical Jacobian
  integer(i4b),intent(out)       :: err                        ! error code
  character(*),intent(out)       :: message                    ! error message
  ! ----------------------------------------------------------------------------------------------------------
  ! local
  character(len=256)             :: cmessage                   ! error message of downwind routine
  real(dp),parameter             :: dx=1.e-8_dp               ! finite difference increment
  real(dp),dimension(nState)     :: stateVecPerturbed          ! perturbed state vector
  real(dp),dimension(nState)     :: fluxVecInit,fluxVecJac     ! flux vector (mized units)
  real(qp),dimension(nState)     :: resVecInit,resVecJac ! qp  ! residual vector (mixed units)
  real(dp)                       :: func                       ! function value
  logical(lgt)                   :: feasible                   ! flag to denote the feasibility of the solution
  integer(i4b)                   :: iJac                       ! index of row of the Jacobian matrix
  integer(i4b),parameter         :: ixNumFlux=1001             ! named variable for the flux-based form of the numerical Jacobian
  integer(i4b),parameter         :: ixNumRes=1002              ! named variable for the residual-based form of the numerical Jacobian
  integer(i4b)                   :: ixNumType=ixNumRes         ! method used to calculate the numerical Jacobian
  ! ----------------------------------------------------------------------------------------------------------
  ! initialize error control
  err=0; message='numJacobian/'

  ! compute initial function evaluation
  call eval8summa_wrapper(stateVec,fluxVecInit,resVecInit,func,feasible,err,cmessage)
  if(err/=0)then; message=trim(message)//trim(cmessage); return; end if  ! (check for errors)
  if(.not.feasible)then; message=trim(message)//'initial state vector not feasible'; err=20; return; endif

  ! get a copy of the state vector to perturb
  stateVecPerturbed(:) = stateVec(:)

  ! loop through state variables
  do iJac=1,nState

   !print*, 'iJac = ', iJac
   !globalPrintFlag = merge(.true.,.false., iJac==1)

   ! perturb state vector
   stateVecPerturbed(iJac) = stateVec(iJac) + dx

   ! compute function evaluation
   call eval8summa_wrapper(stateVecPerturbed,fluxVecJac,resVecJac,func,feasible,err,cmessage)
   if(err/=0)then; message=trim(message)//trim(cmessage); return; end if  ! (check for errors)
   if(.not.feasible)then; message=trim(message)//'state vector not feasible'; err=20; return; endif
   !write(*,'(a,1x,2(f30.20,1x))') 'resVecJac(101:102)  = ', resVecJac(101:102)

   ! compute the row of the Jacobian matrix
   select case(ixNumType)
    case(ixNumRes);  nJac(:,iJac) = real(resVecJac - resVecInit, kind(dp) )/dx  ! Jacobian based on residuals
    case(ixNumFlux); nJac(:,iJac) = -dt*(fluxVecJac(:) - fluxVecInit(:))/dx     ! Jacobian based on fluxes
    case default; err=20; message=trim(message)//'Jacobian option not found'; return
   end select

   ! if flux option then add in the diagonal matrix
   if(ixNumType==ixNumFlux) nJac(iJac,iJac) = nJac(iJac,iJac) + dMat(iJac)

   ! set the state back to the input value
   stateVecPerturbed(iJac) = stateVec(iJac)

  end do  ! (looping through state variables)

  ! print the Jacobian
  print*, '** numerical Jacobian:', ixNumType==ixNumRes
  write(*,'(a4,1x,100(i12,1x))') 'xCol', (iLayer, iLayer=min(iJac1,nState),min(iJac2,nState))
  do iLayer=min(iJac1,nState),min(iJac2,nState)
   write(*,'(i4,1x,100(e12.5,1x))') iLayer, nJac(min(iJac1,nState):min(iJac2,nState),iLayer)
  end do
  !print*, 'PAUSE: testing Jacobian'; read(*,*)

  end subroutine numJacobian

  ! *********************************************************************************************************
  ! * internal subroutine testBandMat: compute the full Jacobian matrix and decompose into a band matrix
  ! *********************************************************************************************************

  subroutine testBandMat(check,err,message)
  ! dummy variables
  logical(lgt),intent(in)         :: check                    ! flag to pause
  integer(i4b),intent(out)        :: err                      ! error code
  character(*),intent(out)        :: message                  ! error message
  ! local variables
  real(dp)                        :: fullJac(nState,nState)   ! full Jacobian matrix
  real(dp)                        :: bandJac(nLeadDim,nState) ! band Jacobian matrix
  integer(i4b)                    :: iState,jState            ! indices of the state vector
  character(LEN=256)              :: cmessage                 ! error message of downwind routine
  ! initialize error control
  err=0; message='testBandMat/'

  ! check
  if(nLeadDim==nState)then
   message=trim(message)//'do not expect nLeadDim==nState: check that are computing the band diagonal matrix'//&
                          ' (is forceFullMatrix==.true.?)'
   err=20; return
  endif

  ! compute the full Jacobian matrix
  call computJacob(&
                   ! input: model control
                   dt,                             & ! intent(in):    length of the time step (seconds)
                   nSnow,                          & ! intent(in):    number of snow layers
                   nSoil,                          & ! intent(in):    number of soil layers
                   nLayers,                        & ! intent(in):    total number of layers
                   computeVegFlux,                 & ! intent(in):    flag to indicate if we need to compute fluxes over vegetation
                   .false.,                        & ! intent(in):    flag to indicate if we need to compute baseflow
                   ixFullMatrix,                   & ! intent(in):    force full Jacobian matrix
                   ! input: data structures
                   indx_data,                      & ! intent(in):    index data
                   prog_data,                      & ! intent(in):    model prognostic variables for a local HRU
                   diag_data,                      & ! intent(in):    model diagnostic variables for a local HRU
                   deriv_data,                     & ! intent(in):    derivatives in model fluxes w.r.t. relevant state variables
                   dBaseflow_dMatric,              & ! intent(in):    derivative in baseflow w.r.t. matric head (s-1)
                   ! input-output: Jacobian and its diagonal
                   dMat,                           & ! intent(inout): diagonal of the Jacobian matrix
                   fullJac,                        & ! intent(out):   full Jacobian matrix
                   ! output: error control
                   err,cmessage)                     ! intent(out):   error code and error message
  if(err/=0)then; message=trim(message)//trim(cmessage); return; end if  ! (check for errors)

  ! initialize band matrix
  bandJac(:,:) = 0._dp

  ! transfer into the lapack band diagonal structure
  do iState=1,nState
   do jState=max(1,iState-ku),min(nState,iState+kl)
    bandJac(kl + ku + 1 + jState - iState, iState) = fullJac(jState,iState)
   end do
  end do

  ! print results
  print*, '** test banded analytical Jacobian:'
  write(*,'(a4,1x,100(i17,1x))') 'xCol', (iState, iState=iJac1,iJac2)
  do iState=kl+1,nLeadDim; write(*,'(i4,1x,100(e17.10,1x))') iState, bandJac(iState,iJac1:iJac2); end do

  ! check if the need to pause
  if(check)then
   print*, 'PAUSE: testing banded analytical Jacobian'
   read(*,*)
  endif

  end subroutine testBandMat



  ! *********************************************************************************************************
  ! * internal subroutine eval8summa_wrapper: compute the right-hand-side vector
  ! *********************************************************************************************************
  ! NOTE: This is simply a wrapper routine for eval8summa, to reduce the number of calling arguments
  !       An internal subroutine, so have access to all data in the main subroutine
  subroutine eval8summa_wrapper(stateVecNew,fluxVecNew,resVecNew,fNew,feasible,err,message)
  USE eval8summa_module,only:eval8summa                      ! simulation of fluxes and residuals given a trial state vector
  implicit none
  ! input
  real(dp),intent(in)            :: stateVecNew(:)           ! updated state vector
  ! output
  real(dp),intent(out)           :: fluxVecNew(:)            ! updated flux vector
  real(qp),intent(out)           :: resVecNew(:) ! NOTE: qp  ! updated residual vector
  real(dp),intent(out)           :: fNew                     ! new function value
  logical(lgt),intent(out)       :: feasible                 ! flag to denote the feasibility of the solution
  integer(i4b),intent(out)       :: err                      ! error code
  character(*),intent(out)       :: message                  ! error message
  ! ----------------------------------------------------------------------------------------------------------
  ! local
  character(len=256)             :: cmessage                 ! error message of downwind routine
  ! ----------------------------------------------------------------------------------------------------------
  ! initialize error control
  err=0; message='eval8summa_wrapper/'

  ! compute the flux and the residual vector for a given state vector
  call eval8summa(&
                  ! input: model control
                  dt,                      & ! intent(in):    length of the time step (seconds)
                  nSnow,                   & ! intent(in):    number of snow layers
                  nSoil,                   & ! intent(in):    number of soil layers
                  nLayers,                 & ! intent(in):    total number of layers
                  nState,                  & ! intent(in):    total number of state variables
                  firstSubStep,            & ! intent(in):    flag to indicate if we are processing the first sub-step
                  firstFluxCall,           & ! intent(inout): flag to indicate if we are processing the first flux call
                  .false.,                 & ! intent(in):    flag to indicate if we are processing the first iteration in a splitting operation
                  computeVegFlux,          & ! intent(in):    flag to indicate if we need to compute fluxes over vegetation
                  scalarSolution,          & ! intent(in):    flag to indicate the scalar solution
                  ! input: state vectors
                  stateVecNew,             & ! intent(in):    updated model state vector
                  fScale,                  & ! intent(in):    function scaling vector
                  sMul,                    & ! intent(in):    state vector multiplier (used in the residual calculations)
                  ! input: data structures
                  model_decisions,         & ! intent(in):    model decisions
                  type_data,               & ! intent(in):    type of vegetation and soil
                  attr_data,               & ! intent(in):    spatial attributes
                  mpar_data,               & ! intent(in):    model parameters
                  forc_data,               & ! intent(in):    model forcing data
                  bvar_data,               & ! intent(in):    average model variables for the entire basin
                  prog_data,               & ! intent(in):    model prognostic variables for a local HRU
                  indx_data,               & ! intent(in):    index data
                  ! input-output: data structures
                  diag_data,               & ! intent(inout): model diagnostic variables for a local HRU
                  flux_data,               & ! intent(inout): model fluxes for a local HRU
                  deriv_data,              & ! intent(inout): derivatives in model fluxes w.r.t. relevant state variables
                  ! input-output: baseflow
                  ixSaturation,            & ! intent(inout): index of the lowest saturated layer (NOTE: only computed on the first iteration)
                  dBaseflow_dMatric,       & ! intent(out):   derivative in baseflow w.r.t. matric head (s-1)
                  ! output
                  feasible,                & ! intent(out):   flag to denote the feasibility of the solution
                  fluxVecNew,              & ! intent(out):   new flux vector
                  resSinkNew,              & ! intent(out):   additional (sink) terms on the RHS of the state equation
                  resVecNew,               & ! intent(out):   new residual vector
                  fNew,                    & ! intent(out):   new function evaluation
                  err,cmessage)              ! intent(out):   error control
  if(err/=0)then; message=trim(message)//trim(cmessage); return; end if  ! (check for errors)

  end subroutine eval8summa_wrapper


  ! *********************************************************************************************************
  ! internal function checkConv: check convergence based on the residual vector
  ! *********************************************************************************************************
  function checkConv(rVec,xInc,xVec)
  implicit none
  ! dummies
  real(qp),intent(in)       :: rVec(:)                ! residual vector (mixed units)
  real(dp),intent(in)       :: xInc(:)                ! iteration increment (mixed units)
  real(dp),intent(in)       :: xVec(:)                ! state vector (mixed units)
  logical(lgt)              :: checkConv              ! flag to denote convergence
  ! locals
  real(dp),dimension(mSoil) :: psiScale               ! scaling factor for matric head
  real(dp),parameter        :: xSmall=1.e-0_dp        ! a small offset
  real(dp),parameter        :: scalarTighten=0.1_dp   ! scaling factor for the scalar solution
  real(dp)                  :: soilWatbalErr          ! error in the soil water balance
  real(dp)                  :: canopy_max             ! absolute value of the residual in canopy water (kg m-2)
  real(dp),dimension(1)     :: energy_max             ! maximum absolute value of the energy residual (J m-3)
  real(dp),dimension(1)     :: liquid_max             ! maximum absolute value of the volumetric liquid water content residual (-)
  real(dp),dimension(1)     :: matric_max             ! maximum absolute value of the matric head iteration increment (m)
  real(dp)                  :: aquifer_max            ! absolute value of the residual in aquifer water (m)
  logical(lgt)              :: canopyConv             ! flag for canopy water balance convergence
  logical(lgt)              :: watbalConv             ! flag for soil water balance convergence
  logical(lgt)              :: liquidConv             ! flag for residual convergence
  logical(lgt)              :: matricConv             ! flag for matric head convergence
  logical(lgt)              :: energyConv             ! flag for energy convergence
  logical(lgt)              :: aquiferConv            ! flag for aquifer water balance convergence
  ! -------------------------------------------------------------------------------------------------------------------------------------------------
  ! association to variables in the data structures
  associate(&
  ! convergence parameters
  absConvTol_liquid       => mpar_data%var(iLookPARAM%absConvTol_liquid)%dat(1)     ,&  ! intent(in): [dp] absolute convergence tolerance for vol frac liq water (-)
  absConvTol_matric       => mpar_data%var(iLookPARAM%absConvTol_matric)%dat(1)     ,&  ! intent(in): [dp] absolute convergence tolerance for matric head        (m)
  absConvTol_energy       => mpar_data%var(iLookPARAM%absConvTol_energy)%dat(1)     ,&  ! intent(in): [dp] absolute convergence tolerance for energy             (J m-3)
  ! layer depth
  mLayerDepth             => prog_data%var(iLookPROG%mLayerDepth)%dat               ,&  ! intent(in): [dp(:)] depth of each layer in the snow-soil sub-domain (m)
  ! model indices
  ixAqWat                 => indx_data%var(iLookINDEX%ixAqWat)%dat(1)               ,&  ! intent(in): [i4b]    index of aquifer storage state variable
  ixCasNrg                => indx_data%var(iLookINDEX%ixCasNrg)%dat(1)              ,&  ! intent(in): [i4b]    index of canopy air space energy state variable
  ixVegNrg                => indx_data%var(iLookINDEX%ixVegNrg)%dat(1)              ,&  ! intent(in): [i4b]    index of canopy energy state variable
  ixVegHyd                => indx_data%var(iLookINDEX%ixVegHyd)%dat(1)              ,&  ! intent(in): [i4b]    index of canopy hydrology state variable (mass)
  ixNrgOnly               => indx_data%var(iLookINDEX%ixNrgOnly)%dat                ,&  ! intent(in): [i4b(:)] list of indices for all energy states
  ixHydOnly               => indx_data%var(iLookINDEX%ixHydOnly)%dat                ,&  ! intent(in): [i4b(:)] list of indices for all hydrology states
  ixMatOnly               => indx_data%var(iLookINDEX%ixMatOnly)%dat                ,&  ! intent(in): [i4b(:)] list of indices for matric head state variables in the state vector
  ixMatricHead            => indx_data%var(iLookINDEX%ixMatricHead)%dat              &  ! intent(in): [i4b(:)] list of indices for matric head in the soil vector

  ) ! making associations with variables in the data structures
  ! -------------------------------------------------------------------------------------------------------------------------------------------------

  ! check convergence based on the canopy water balance
  if(ixVegHyd/=integerMissing)then
   canopy_max = real(abs(rVec(ixVegHyd)), dp)*iden_water
   canopyConv = (canopy_max    < absConvTol_liquid)  ! absolute error in canopy water balance (mm)
  else
   canopy_max = realMissing
   canopyConv = .true.
  endif

  ! check convergence based on the residuals for energy (J m-3)
  if(size(ixNrgOnly)>0)then
   energy_max = real(maxval(abs( rVec(ixNrgOnly) )), dp)
   energyConv = (energy_max(1) < absConvTol_energy)  ! (based on the residual)
  else
   energy_max = realMissing
   energyConv = .true.
  endif

  ! check convergence based on the residuals for volumetric liquid water content (-)
  if(size(ixHydOnly)>0)then
   liquid_max = real(maxval(abs( rVec(ixHydOnly) ) ), dp)
   ! (tighter convergence for the scalar solution)
   if(scalarSolution)then
    liquidConv = (liquid_max(1) < absConvTol_liquid*scalarTighten)   ! (based on the residual)
   else
    liquidConv = (liquid_max(1) < absConvTol_liquid)                 ! (based on the residual)
   endif
  else
   liquid_max = realMissing
   liquidConv = .true.
  endif

  ! check convergence based on the iteration increment for matric head
  ! NOTE: scale by matric head to avoid unnecessairly tight convergence when there is no water
  if(size(ixMatOnly)>0)then
   psiScale   = abs( xVec(ixMatOnly) ) + xSmall ! avoid divide by zero
   matric_max = maxval(abs( xInc(ixMatOnly)/psiScale ) )
   matricConv = (matric_max(1) < absConvTol_matric)  ! NOTE: based on iteration increment
  else
   matric_max = realMissing
   matricConv = .true.
  endif

  ! check convergence based on the soil water balance error (m)
  if(size(ixMatOnly)>0)then
   soilWatBalErr = sum( real(rVec(ixMatOnly), dp)*mLayerDepth(nSnow+ixMatricHead) )
   watbalConv    = (abs(soilWatbalErr) < absConvTol_liquid)  ! absolute error in total soil water balance (m)
  else
   soilWatbalErr = realMissing
   watbalConv    = .true.
  endif

  ! check convergence based on the aquifer storage
  if(ixAqWat/=integerMissing)then
   aquifer_max = real(abs(rVec(ixAqWat)), dp)*iden_water
   aquiferConv = (aquifer_max    < absConvTol_liquid)  ! absolute error in aquifer water balance (mm)
  else
   aquifer_max = realMissing
   aquiferConv = .true.
  endif

  ! final convergence check
  checkConv = (canopyConv .and. watbalConv .and. matricConv .and. liquidConv .and. energyConv .and. aquiferConv)

  ! print progress towards solution
  if(globalPrintFlag)then
   write(*,'(a,1x,i4,1x,7(e15.5,1x),7(L1,1x))') 'check convergence: ', iter, &
    fNew, matric_max(1), liquid_max(1), energy_max(1), canopy_max, aquifer_max, soilWatBalErr, matricConv, liquidConv, energyConv, watbalConv, canopyConv, aquiferConv, watbalConv
  endif

  ! end associations with variables in the data structures
  end associate

  end function checkConv


  ! *********************************************************************************************************
  ! internal subroutine imposeConstraints: impose solution constraints
  ! *********************************************************************************************************
  subroutine imposeConstraints(stateVecTrial,xInc,err,message)
  ! external functions
  USE snow_utils_module,only:fracliquid                           ! compute the fraction of liquid water at a given temperature (snow)
  USE soil_utils_module,only:crit_soilT                           ! compute the critical temperature below which ice exists
  implicit none
  ! dummies
  real(dp),intent(in)             :: stateVecTrial(:)             ! trial state vector
  real(dp),intent(inout)          :: xInc(:)                      ! iteration increment
  integer(i4b),intent(out)        :: err                          ! error code
  character(*),intent(out)        :: message                      ! error message
  ! -----------------------------------------------------------------------------------------------------
  ! temporary variables for model constraints
  real(dp)                        :: cInc                         ! constrained temperature increment (K) -- simplified bi-section
  real(dp)                        :: xIncFactor                   ! scaling factor for the iteration increment (-)
  integer(i4b)                    :: iMax(1)                      ! index of maximum temperature
  real(dp)                        :: scalarTemp                   ! temperature of an individual snow layer (K)
  real(dp)                        :: volFracLiq                   ! volumetric liquid water content of an individual snow layer (-)
  logical(lgt),dimension(nSnow)   :: drainFlag                    ! flag to denote when drainage exceeds available capacity
  logical(lgt),dimension(nSoil)   :: crosFlag                     ! flag to denote temperature crossing from unfrozen to frozen (or vice-versa)
  logical(lgt)                    :: crosTempVeg                  ! flag to denoote where temperature crosses the freezing point
  real(dp)                        :: xPsi00                       ! matric head after applying the iteration increment (m)
  real(dp)                        :: TcSoil                       ! critical point when soil begins to freeze (K)
  real(dp)                        :: critDiff                     ! temperature difference from critical (K)
  real(dp),parameter              :: epsT=1.e-7_dp                ! small interval above/below critical (K)
  real(dp),parameter              :: zMaxTempIncrement=1._dp      ! maximum temperature increment (K)
  ! indices of model state variables
  integer(i4b)                    :: iState                       ! index of state within a specific variable type
  integer(i4b)                    :: ixNrg,ixLiq                  ! index of energy and mass state variables in full state vector
  ! indices of model layers
  integer(i4b)                    :: iLayer                       ! index of model layer
  ! -----------------------------------------------------------------------------------------------------
  ! associate variables with indices of model state variables
  associate(&
  ixNrgOnly               => indx_data%var(iLookINDEX%ixNrgOnly)%dat                ,& ! intent(in): [i4b(:)] list of indices in the state subset for energy states
  ixHydOnly               => indx_data%var(iLookINDEX%ixHydOnly)%dat                ,& ! intent(in): [i4b(:)] list of indices in the state subset for hydrology states
  ixMatOnly               => indx_data%var(iLookINDEX%ixMatOnly)%dat                ,& ! intent(in): [i4b(:)] list of indices in the state subset for matric head states
  ixMassOnly              => indx_data%var(iLookINDEX%ixMassOnly)%dat               ,& ! intent(in): [i4b(:)] list of indices in the state subset for canopy storage states
  ixStateType_subset      => indx_data%var(iLookINDEX%ixStateType_subset)%dat       ,& ! intent(in): [i4b(:)] named variables defining the states in the subset
  ! indices for specific state variables
  ixCasNrg                => indx_data%var(iLookINDEX%ixCasNrg)%dat(1)              ,& ! intent(in): [i4b] index of canopy air space energy state variable
  ixVegNrg                => indx_data%var(iLookINDEX%ixVegNrg)%dat(1)              ,& ! intent(in): [i4b] index of canopy energy state variable
  ixVegHyd                => indx_data%var(iLookINDEX%ixVegHyd)%dat(1)              ,& ! intent(in): [i4b] index of canopy hydrology state variable (mass)
  ixTopNrg                => indx_data%var(iLookINDEX%ixTopNrg)%dat(1)              ,& ! intent(in): [i4b] index of upper-most energy state in the snow-soil subdomain
  ixTopHyd                => indx_data%var(iLookINDEX%ixTopHyd)%dat(1)              ,& ! intent(in): [i4b] index of upper-most hydrology state in the snow-soil subdomain
  ! vector of energy indices for the snow and soil domains
  ! NOTE: states not in the subset are equal to integerMissing
  ixSnowSoilNrg           => indx_data%var(iLookINDEX%ixSnowSoilNrg)%dat            ,& ! intent(in): [i4b(:)] index in the state subset for energy state variables in the snow+soil domain
  ixSnowOnlyNrg           => indx_data%var(iLookINDEX%ixSnowOnlyNrg)%dat            ,& ! intent(in): [i4b(:)] index in the state subset for energy state variables in the snow domain
  ixSoilOnlyNrg           => indx_data%var(iLookINDEX%ixSoilOnlyNrg)%dat            ,& ! intent(in): [i4b(:)] index in the state subset for energy state variables in the soil domain
  ! vector of hydrology indices for the snow and soil domains
  ! NOTE: states not in the subset are equal to integerMissing
  ixSnowSoilHyd           => indx_data%var(iLookINDEX%ixSnowSoilHyd)%dat            ,& ! intent(in): [i4b(:)] index in the state subset for hydrology state variables in the snow+soil domain
  ixSnowOnlyHyd           => indx_data%var(iLookINDEX%ixSnowOnlyHyd)%dat            ,& ! intent(in): [i4b(:)] index in the state subset for hydrology state variables in the snow domain
  ixSoilOnlyHyd           => indx_data%var(iLookINDEX%ixSoilOnlyHyd)%dat            ,& ! intent(in): [i4b(:)] index in the state subset for hydrology state variables in the soil domain
  ! number of state variables of a specific type
  nSnowSoilNrg            => indx_data%var(iLookINDEX%nSnowSoilNrg )%dat(1)         ,& ! intent(in): [i4b]    number of energy state variables in the snow+soil domain
  nSnowOnlyNrg            => indx_data%var(iLookINDEX%nSnowOnlyNrg )%dat(1)         ,& ! intent(in): [i4b]    number of energy state variables in the snow domain
  nSoilOnlyNrg            => indx_data%var(iLookINDEX%nSoilOnlyNrg )%dat(1)         ,& ! intent(in): [i4b]    number of energy state variables in the soil domain
  nSnowSoilHyd            => indx_data%var(iLookINDEX%nSnowSoilHyd )%dat(1)         ,& ! intent(in): [i4b]    number of hydrology variables in the snow+soil domain
  nSnowOnlyHyd            => indx_data%var(iLookINDEX%nSnowOnlyHyd )%dat(1)         ,& ! intent(in): [i4b]    number of hydrology variables in the snow domain
  nSoilOnlyHyd            => indx_data%var(iLookINDEX%nSoilOnlyHyd )%dat(1)         ,& ! intent(in): [i4b]    number of hydrology variables in the soil domain
  ! state variables at the start of the time step
  mLayerMatricHead        => prog_data%var(iLookPROG%mLayerMatricHead)%dat           & ! intent(in): [dp(:)] matric head (m)
  ) ! associating variables with indices of model state variables
  ! -----------------------------------------------------------------------------------------------------
  ! initialize error control
  err=0; message='imposeConstraints/'

  ! ** limit temperature increment to zMaxTempIncrement
  if(any(abs(xInc(ixNrgOnly)) > zMaxTempIncrement))then
   iMax       = maxloc( abs(xInc(ixNrgOnly)) )                     ! index of maximum temperature increment
   xIncFactor = abs( zMaxTempIncrement/xInc(ixNrgOnly(iMax(1))) )  ! scaling factor for the iteration increment (-)
   xInc       = xIncFactor*xInc
  end if

  ! ** impose solution constraints for vegetation
  ! (stop just above or just below the freezing point if crossing)
  ! --------------------------------------------------------------------------------------------------------------------
  ! canopy temperatures

  if(ixVegNrg/=integerMissing)then

   ! initialize
   critDiff    = Tfreeze - stateVecTrial(ixVegNrg)
   crosTempVeg = .false.

   ! initially frozen (T < Tfreeze)
   if(critDiff > 0._dp)then
    if(xInc(ixVegNrg) > critDiff)then
     crosTempVeg = .true.
     cInc        = critDiff + epsT  ! constrained temperature increment (K)
    end if

   ! initially unfrozen (T > Tfreeze)
   else
    if(xInc(ixVegNrg) < critDiff)then
     crosTempVeg = .true.
     cInc        = critDiff - epsT  ! constrained temperature increment (K)
    end if

   end if  ! switch between frozen and unfrozen

   ! scale iterations
   if(crosTempVeg)then
    xIncFactor  = cInc/xInc(ixVegNrg)  ! scaling factor for the iteration increment (-)
    xInc        = xIncFactor*xInc      ! scale iteration increments
   endif

  endif  ! if the state variable for canopy temperature is included within the state subset

  ! --------------------------------------------------------------------------------------------------------------------
  ! canopy liquid water

  if(ixVegHyd/=integerMissing)then

   ! check if new value of storage will be negative
   if(stateVecTrial(ixVegHyd)+xInc(ixVegHyd) < 0._dp)then
    ! scale iteration increment
    cInc       = -0.5_dp*stateVecTrial(ixVegHyd)                                  ! constrained iteration increment (K) -- simplified bi-section
    xIncFactor = cInc/xInc(ixVegHyd)                                              ! scaling factor for the iteration increment (-)
    xInc       = xIncFactor*xInc                                                  ! new iteration increment
   end if

  endif  ! if the state variable for canopy water is included within the state subset

  ! --------------------------------------------------------------------------------------------------------------------
  ! ** impose solution constraints for snow
  if(nSnowOnlyNrg > 0)then

   ! loop through snow layers
   checksnow: do iLayer=1,nSnow  ! necessary to ensure that NO layers rise above Tfreeze

    ! check of the data is mising
    if(ixSnowOnlyNrg(iLayer)==integerMissing) cycle

    ! check temperatures, and, if necessary, scale iteration increment
    iState = ixSnowOnlyNrg(iLayer)
    if(stateVecTrial(iState) + xInc(iState) > Tfreeze)then
     ! scale iteration increment
     cInc       = 0.5_dp*(Tfreeze - stateVecTrial(iState) )        ! constrained temperature increment (K) -- simplified bi-section
     xIncFactor = cInc/xInc(iState)                                ! scaling factor for the iteration increment (-)
     xInc       = xIncFactor*xInc
    end if   ! if snow temperature > freezing

   end do checkSnow

  endif  ! if there are state variables for energy in the snow domain

  ! --------------------------------------------------------------------------------------------------------------------
  ! - check if drain more than what is available
  ! NOTE: change in total water is only due to liquid flux
  if(nSnowOnlyHyd>0)then

   ! loop through snow layers
   do iLayer=1,nSnow

    ! * check if the layer is included
    if(ixSnowOnlyHyd(iLayer)==integerMissing) cycle

    ! * get the layer temperature (from stateVecTrial if ixSnowOnlyNrg(iLayer) is within the state vector
    if(ixSnowOnlyNrg(iLayer)/=integerMissing)then
     scalarTemp = stateVecTrial( ixSnowOnlyNrg(iLayer) )

    ! * get the layer temperature from the last update
    else
     scalarTemp = prog_data%var(iLookPROG%mLayerTemp)%dat(iLayer)
    endif

    ! * get the volumetric fraction of liquid water
    select case( ixStateType_subset( ixSnowOnlyHyd(iLayer) ) )
     case(iname_watLayer); volFracLiq = fracliquid(scalarTemp,mpar_data%var(iLookPARAM%snowfrz_scale)%dat(1)) * stateVecTrial(ixSnowOnlyHyd(iLayer))
     case(iname_liqLayer); volFracLiq = stateVecTrial(ixSnowOnlyHyd(iLayer))
     case default; err=20; message=trim(message)//'expect ixStateType_subset to be iname_watLayer or iname_liqLayer for snow hydrology'; return
    end select

    ! * check that the iteration increment does not exceed volumetric liquid water content
    if(-xInc(ixSnowOnlyHyd(iLayer)) > volFracLiq)then
     drainFlag(iLayer) = .true.
     xInc(ixSnowOnlyHyd(iLayer)) = -0.5_dp*volFracLiq
    endif

   end do  ! looping through snow layers

  endif   ! if there are state variables for liquid water in the snow domain

  ! --------------------------------------------------------------------------------------------------------------------
  ! ** impose solution constraints for soil temperature
  if(nSoilOnlyNrg>0)then
   do iLayer=1,nSoil

    ! - check if energy state is included
    if(ixSoilOnlyNrg(iLayer)==integerMissing) cycle

    ! - define index of the state variables within the state subset
    ixNrg = ixSoilOnlyNrg(iLayer)
    ixLiq = ixSoilOnlyHyd(iLayer)

    ! get the matric potential of total water
    if(ixLiq/=integerMissing)then
     xPsi00 = stateVecTrial(ixLiq) + xInc(ixLiq)
    else
     xPsi00 = mLayerMatricHead(iLayer)
    endif

    ! identify the critical point when soil begins to freeze (TcSoil)
    TcSoil = crit_soilT(xPsi00)

    ! get the difference from the current state and the crossing point (K)
    critDiff = TcSoil - stateVecTrial(ixNrg)

    ! * initially frozen (T < TcSoil)
    if(critDiff > 0._dp)then

     ! (check crossing above zero)
     if(xInc(ixNrg) > critDiff)then
      crosFlag(iLayer) = .true.
      xInc(ixNrg) = critDiff + epsT  ! set iteration increment to slightly above critical temperature
     endif

    ! * initially unfrozen (T > TcSoil)
    else

     ! (check crossing below zero)
     if(xInc(ixNrg) < critDiff)then
      crosFlag(iLayer) = .true.
      xInc(ixNrg) = critDiff - epsT  ! set iteration increment to slightly below critical temperature
     endif

    endif  ! (switch between initially frozen and initially unfrozen)

   end do  ! (loop through soil layers)
  endif   ! (if there are both energy and liquid water state variables)

  ! ** impose solution constraints matric head
  if(size(ixMatOnly)>0)then
   do iState=1,size(ixMatOnly)

    ! - define index of the hydrology state variable within the state subset
    ixLiq = ixMatOnly(iState)

    ! - place constraint for matric head
    if(xInc(ixLiq) > 1._dp .and. stateVecTrial(ixLiq) > 0._dp)then
     xInc(ixLiq) = 1._dp
    endif  ! if constraining matric head

   end do  ! (loop through soil layers)
  endif   ! (if there are both energy and liquid water state variables)

  ! end association with variables with indices of model state variables
  end associate

  end subroutine imposeConstraints

 end subroutine summaSolve




end module summaSolve_module