Skip to content
Snippets Groups Projects
computJacob.f90 50.49 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 computJacob_module

! data types
USE nrtype

! derived types to define the data structures
USE data_types,only:&
                    var_ilength,  & ! data vector with variable length dimension (i4b)
                    var_dlength     ! data vector with variable length dimension (dp)

! named variables for structure elements
USE var_lookup,only:iLookPROG       ! named variables for structure elements
USE var_lookup,only:iLookDIAG       ! named variables for structure elements
USE var_lookup,only:iLookINDEX      ! named variables for structure elements
USE var_lookup,only:iLookDERIV      ! named variables for structure elements

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

! access missing values
USE globalData,only:integerMissing  ! missing integer
USE globalData,only:realMissing     ! missing real number

! domain types
USE globalData,only:iname_veg       ! named variables for vegetation
USE globalData,only:iname_snow      ! named variables for snow
USE globalData,only:iname_soil      ! named variables for soil

! 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

! 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: ixDiag         ! index for the diagonal band
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

! constants
USE multiconst,only:&
                    LH_fus,       & ! latent heat of fusion                (J kg-1)
                    iden_ice,     & ! intrinsic density of ice             (kg m-3)
                    iden_water      ! intrinsic density of liquid water    (kg m-3)

implicit none
! define constants
real(dp),parameter     :: verySmall=tiny(1.0_dp)     ! a very small number
integer(i4b),parameter :: ixBandOffset=kl+ku+1       ! offset in the band Jacobian matrix

private
public::computJacob
contains

 ! **********************************************************************************************************
 ! public subroutine computJacob: compute the Jacobian matrix
 ! **********************************************************************************************************
 subroutine 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
                        computeBaseflow,            & ! 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,message)                  ! intent(out):   error code and error message
 ! -----------------------------------------------------------------------------------------------------------------
 implicit none
 ! input: model control
 real(dp),intent(in)               :: dt              ! length of the time step (seconds)
 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 in the snow+soil domain
 logical(lgt),intent(in)           :: computeVegFlux  ! flag to indicate if computing fluxes over vegetation
 logical(lgt),intent(in)           :: computeBaseflow ! flag to indicate if computing baseflow
 integer(i4b),intent(in)           :: ixMatrix        ! form of the Jacobian matrix
 ! input: data structures
 type(var_ilength),intent(in)      :: indx_data       ! indices defining model states and layers
 type(var_dlength),intent(in)      :: prog_data       ! prognostic variables for a local HRU
 type(var_dlength),intent(in)      :: diag_data       ! diagnostic variables for a local HRU
 type(var_dlength),intent(in)      :: deriv_data      ! derivatives in model fluxes w.r.t. relevant state variables
 real(dp),intent(in)               :: dBaseflow_dMatric(:,:) ! derivative in baseflow w.r.t. matric head (s-1)
 ! input-output: Jacobian and its diagonal
 real(dp),intent(inout)            :: dMat(:)         ! diagonal of the Jacobian matrix
 real(dp),intent(out)              :: aJac(:,:)       ! Jacobian matrix
 ! output variables
 integer(i4b),intent(out)          :: err             ! error code
 character(*),intent(out)          :: message         ! error message
 ! --------------------------------------------------------------
 ! * local variables
 ! --------------------------------------------------------------
 ! indices of model state variables
 integer(i4b)                      :: jState          ! index of state within the state subset
 integer(i4b)                      :: qState          ! index of cross-derivative state variable for baseflow
 integer(i4b)                      :: nrgState        ! energy state variable
 integer(i4b)                      :: watState        ! hydrology state variable
 integer(i4b)                      :: nState          ! number of state variables
 ! indices of model layers
 integer(i4b)                      :: iLayer          ! index of model layer
 integer(i4b)                      :: jLayer          ! index of model layer within the full state vector (hydrology)
 integer(i4b)                      :: pLayer          ! indices of soil layers (used for the baseflow derivatives)
 ! conversion factors
 real(dp)                          :: convLiq2tot     ! factor to convert liquid water derivative to total water derivative
 ! --------------------------------------------------------------
 ! associate variables from data structures
 associate(&
 ! indices of model 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
 ixAqWat                      => indx_data%var(iLookINDEX%ixAqWat)%dat(1)                        ,& ! intent(in): [i4b] index of water storage in the aquifer
 ! vectors of indices for specfic state types within specific sub-domains IN THE FULL STATE VECTOR
 ixNrgLayer                   => indx_data%var(iLookINDEX%ixNrgLayer)%dat                        ,& ! intent(in): [i4b(:)] indices IN THE FULL VECTOR for energy states in the snow+soil domain
 ixHydLayer                   => indx_data%var(iLookINDEX%ixHydLayer)%dat                        ,& ! intent(in): [i4b(:)] indices IN THE FULL VECTOR for hydrology states in the snow+soil domain
 ! 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
 ! type and index of model control volume
 ixHydType                    => indx_data%var(iLookINDEX%ixHydType)%dat                         ,& ! intent(in): [i4b(:)] index of the type of hydrology states in snow+soil domain
 ixDomainType                 => indx_data%var(iLookINDEX%ixDomainType)%dat                      ,& ! intent(in): [i4b(:)] indices defining the type of the domain (iname_veg, iname_snow, iname_soil)
 ixControlVolume              => indx_data%var(iLookINDEX%ixControlVolume)%dat                   ,& ! intent(in): [i4b(:)] index of the control volume for specific model domains
 ! mapping between states and model layers
 ixMapSubset2Full             => indx_data%var(iLookINDEX%ixMapSubset2Full)%dat                  ,& ! intent(in): [i4b(:)] list of indices in the full state vector that are in the state subset
 ixMapFull2Subset             => indx_data%var(iLookINDEX%ixMapFull2Subset)%dat                  ,& ! intent(in): [i4b(:)] list of indices in the state subset in each element of the full state vector
 ! derivatives in net vegetation energy fluxes w.r.t. relevant state variables
 dCanairNetFlux_dCanairTemp   => deriv_data%var(iLookDERIV%dCanairNetFlux_dCanairTemp  )%dat(1)  ,& ! intent(in): [dp]     derivative in net canopy air space flux w.r.t. canopy air temperature
 dCanairNetFlux_dCanopyTemp   => deriv_data%var(iLookDERIV%dCanairNetFlux_dCanopyTemp  )%dat(1)  ,& ! intent(in): [dp]     derivative in net canopy air space flux w.r.t. canopy temperature
 dCanairNetFlux_dGroundTemp   => deriv_data%var(iLookDERIV%dCanairNetFlux_dGroundTemp  )%dat(1)  ,& ! intent(in): [dp]     derivative in net canopy air space flux w.r.t. ground temperature
 dCanopyNetFlux_dCanairTemp   => deriv_data%var(iLookDERIV%dCanopyNetFlux_dCanairTemp  )%dat(1)  ,& ! intent(in): [dp]     derivative in net canopy flux w.r.t. canopy air temperature
 dCanopyNetFlux_dCanopyTemp   => deriv_data%var(iLookDERIV%dCanopyNetFlux_dCanopyTemp  )%dat(1)  ,& ! intent(in): [dp]     derivative in net canopy flux w.r.t. canopy temperature
 dCanopyNetFlux_dGroundTemp   => deriv_data%var(iLookDERIV%dCanopyNetFlux_dGroundTemp  )%dat(1)  ,& ! intent(in): [dp]     derivative in net canopy flux w.r.t. ground temperature
 dCanopyNetFlux_dCanLiq       => deriv_data%var(iLookDERIV%dCanopyNetFlux_dCanLiq      )%dat(1)  ,& ! intent(in): [dp]     derivative in net canopy fluxes w.r.t. canopy liquid water content
 dGroundNetFlux_dCanairTemp   => deriv_data%var(iLookDERIV%dGroundNetFlux_dCanairTemp  )%dat(1)  ,& ! intent(in): [dp]     derivative in net ground flux w.r.t. canopy air temperature
 dGroundNetFlux_dCanopyTemp   => deriv_data%var(iLookDERIV%dGroundNetFlux_dCanopyTemp  )%dat(1)  ,& ! intent(in): [dp]     derivative in net ground flux w.r.t. canopy temperature
 dGroundNetFlux_dCanLiq       => deriv_data%var(iLookDERIV%dGroundNetFlux_dCanLiq      )%dat(1)  ,& ! intent(in): [dp]     derivative in net ground fluxes w.r.t. canopy liquid water content
 ! derivatives in evaporative fluxes w.r.t. relevant state variables
 dCanopyEvaporation_dTCanair  => deriv_data%var(iLookDERIV%dCanopyEvaporation_dTCanair )%dat(1)  ,& ! intent(in): [dp]     derivative in canopy evaporation w.r.t. canopy air temperature
 dCanopyEvaporation_dTCanopy  => deriv_data%var(iLookDERIV%dCanopyEvaporation_dTCanopy )%dat(1)  ,& ! intent(in): [dp]     derivative in canopy evaporation w.r.t. canopy temperature
 dCanopyEvaporation_dTGround  => deriv_data%var(iLookDERIV%dCanopyEvaporation_dTGround )%dat(1)  ,& ! intent(in): [dp]     derivative in canopy evaporation w.r.t. ground temperature
 dCanopyEvaporation_dCanLiq   => deriv_data%var(iLookDERIV%dCanopyEvaporation_dCanLiq  )%dat(1)  ,& ! intent(in): [dp]     derivative in canopy evaporation w.r.t. canopy liquid water content
 dGroundEvaporation_dTCanair  => deriv_data%var(iLookDERIV%dGroundEvaporation_dTCanair )%dat(1)  ,& ! intent(in): [dp]     derivative in ground evaporation w.r.t. canopy air temperature
 dGroundEvaporation_dTCanopy  => deriv_data%var(iLookDERIV%dGroundEvaporation_dTCanopy )%dat(1)  ,& ! intent(in): [dp]     derivative in ground evaporation w.r.t. canopy temperature
 dGroundEvaporation_dTGround  => deriv_data%var(iLookDERIV%dGroundEvaporation_dTGround )%dat(1)  ,& ! intent(in): [dp]     derivative in ground evaporation w.r.t. ground temperature
 dGroundEvaporation_dCanLiq   => deriv_data%var(iLookDERIV%dGroundEvaporation_dCanLiq  )%dat(1)  ,& ! intent(in): [dp]     derivative in ground evaporation w.r.t. canopy liquid water content
 ! derivatives in canopy water w.r.t canopy temperature
 dCanLiq_dTcanopy             => deriv_data%var(iLookDERIV%dCanLiq_dTcanopy            )%dat(1)  ,& ! intent(in): [dp]     derivative of canopy liquid storage w.r.t. temperature
 dTheta_dTkCanopy             => deriv_data%var(iLookDERIV%dTheta_dTkCanopy            )%dat(1)  ,& ! intent(in): [dp]     derivative of volumetric liquid water content w.r.t. temperature
 ! derivatives in canopy liquid fluxes w.r.t. canopy water
 scalarCanopyLiqDeriv         => deriv_data%var(iLookDERIV%scalarCanopyLiqDeriv        )%dat(1)  ,& ! intent(in): [dp]     derivative in (throughfall + drainage) w.r.t. canopy liquid water
 ! derivatives in energy fluxes at the interface of snow+soil layers w.r.t. temperature in layers above and below
 dNrgFlux_dTempAbove          => deriv_data%var(iLookDERIV%dNrgFlux_dTempAbove         )%dat     ,& ! intent(in): [dp(:)]  derivatives in the flux w.r.t. temperature in the layer above
 dNrgFlux_dTempBelow          => deriv_data%var(iLookDERIV%dNrgFlux_dTempBelow         )%dat     ,& ! intent(in): [dp(:)]  derivatives in the flux w.r.t. temperature in the layer below
 ! derivative in liquid water fluxes at the interface of snow layers w.r.t. volumetric liquid water content in the layer above
 iLayerLiqFluxSnowDeriv       => deriv_data%var(iLookDERIV%iLayerLiqFluxSnowDeriv      )%dat     ,& ! intent(in): [dp(:)]  derivative in vertical liquid water flux at layer interfaces
 ! derivative in liquid water fluxes for the soil domain w.r.t hydrology state variables
 dVolTot_dPsi0                => deriv_data%var(iLookDERIV%dVolTot_dPsi0               )%dat     ,& ! intent(in): [dp(:)]  derivative in total water content w.r.t. total water matric potential
 dq_dHydStateAbove            => deriv_data%var(iLookDERIV%dq_dHydStateAbove           )%dat     ,& ! intent(in): [dp(:)]  change in flux at layer interfaces w.r.t. states in the layer above
 dq_dHydStateBelow            => deriv_data%var(iLookDERIV%dq_dHydStateBelow           )%dat     ,& ! intent(in): [dp(:)]  change in flux at layer interfaces w.r.t. states in the layer below
 dCompress_dPsi               => deriv_data%var(iLookDERIV%dCompress_dPsi              )%dat     ,& ! intent(in): [dp(:)]  derivative in compressibility w.r.t matric head
 ! derivative in baseflow flux w.r.t. aquifer storage
 dBaseflow_dAquifer           => deriv_data%var(iLookDERIV%dBaseflow_dAquifer          )%dat(1)  ,&  ! intent(out): [dp(:)] erivative in baseflow flux w.r.t. aquifer storage (s-1)
 ! derivative in liquid water fluxes for the soil domain w.r.t energy state variables
 dq_dNrgStateAbove            => deriv_data%var(iLookDERIV%dq_dNrgStateAbove           )%dat     ,& ! intent(in): [dp(:)]  change in flux at layer interfaces w.r.t. states in the layer above
 dq_dNrgStateBelow            => deriv_data%var(iLookDERIV%dq_dNrgStateBelow           )%dat     ,& ! intent(in): [dp(:)]  change in flux at layer interfaces w.r.t. states in the layer below
 mLayerdTheta_dTk             => deriv_data%var(iLookDERIV%mLayerdTheta_dTk            )%dat     ,& ! intent(in): [dp(:)]  derivative of volumetric liquid water content w.r.t. temperature
 ! diagnostic variables
 scalarFracLiqVeg             => diag_data%var(iLookDIAG%scalarFracLiqVeg)%dat(1)                ,& ! intent(in): [dp]     fraction of liquid water on vegetation (-)
 scalarBulkVolHeatCapVeg      => diag_data%var(iLookDIAG%scalarBulkVolHeatCapVeg)%dat(1)         ,& ! intent(in): [dp]     bulk volumetric heat capacity of vegetation (J m-3 K-1)
 mLayerFracLiqSnow            => diag_data%var(iLookDIAG%mLayerFracLiqSnow)%dat                  ,& ! intent(in): [dp(:)]  fraction of liquid water in each snow layer (-)
 mLayerVolHtCapBulk           => diag_data%var(iLookDIAG%mLayerVolHtCapBulk)%dat                 ,& ! intent(in): [dp(:)]  bulk volumetric heat capacity in each snow and soil layer (J m-3 K-1)
 scalarSoilControl            => diag_data%var(iLookDIAG%scalarSoilControl)%dat(1)               ,& ! intent(in): [dp]     soil control on infiltration, zero or one
 ! canopy and layer depth
 canopyDepth                  => diag_data%var(iLookDIAG%scalarCanopyDepth)%dat(1)               ,& ! intent(in): [dp   ]  canopy depth (m)
 mLayerDepth                  => prog_data%var(iLookPROG%mLayerDepth)%dat                         & ! intent(in): [dp(:)]  depth of each layer in the snow-soil sub-domain (m)
 ) ! making association with data in structures
 ! --------------------------------------------------------------
 ! initialize error control
 err=0; message='computJacob/'

 ! *********************************************************************************************************************************************************
 ! *********************************************************************************************************************************************************
 ! * PART 0: PRELIMINARIES (INITIALIZE JACOBIAN AND COMPUTE TIME-VARIABLE DIAGONAL TERMS)
 ! *********************************************************************************************************************************************************
 ! *********************************************************************************************************************************************************

 ! get the number of state variables
 nState = size(dMat)

 ! initialize the Jacobian
 ! NOTE: this needs to be done every time, since Jacobian matrix is modified in the solver
 aJac(:,:) = 0._dp  ! analytical Jacobian matrix

 ! compute terms in the Jacobian for vegetation (excluding fluxes)
 ! NOTE: energy for vegetation is computed *within* the iteration loop as it includes phase change
 if(ixVegNrg/=integerMissing) dMat(ixVegNrg) = scalarBulkVolHeatCapVeg + LH_fus*iden_water*dTheta_dTkCanopy       ! volumetric heat capacity of the vegetation (J m-3 K-1)

 ! compute additional terms for the Jacobian for the snow-soil domain (excluding fluxes)
 ! NOTE: energy for snow+soil is computed *within* the iteration loop as it includes phase change
 do iLayer=1,nLayers
  if(ixSnowSoilNrg(iLayer)/=integerMissing) dMat(ixSnowSoilNrg(iLayer)) = mLayerVolHtCapBulk(iLayer) + LH_fus*iden_water*mLayerdTheta_dTk(iLayer)
 end do

 ! compute additional terms for the Jacobian for the soil domain (excluding fluxes)
 do iLayer=1,nSoil
  if(ixSoilOnlyHyd(iLayer)/=integerMissing) dMat(ixSoilOnlyHyd(iLayer)) = dVolTot_dPsi0(iLayer) + dCompress_dPsi(iLayer)
 end do

 ! define the form of the matrix
 select case(ixMatrix)

  ! *********************************************************************************************************************************************************
  ! *********************************************************************************************************************************************************
  ! * PART 1: BAND MATRIX
  ! *********************************************************************************************************************************************************
  ! *********************************************************************************************************************************************************
  case(ixBandMatrix)

   ! check
   if(size(aJac,1)/=nBands .or. size(aJac,2)/=size(dMat))then
    message=trim(message)//'unexpected shape of the Jacobian matrix: expect aJac(nBands,nState)'
    err=20; return
   end if

   ! -----
   ! * energy and liquid fluxes over vegetation...
   ! ---------------------------------------------
   if(computeVegFlux)then  ! (derivatives only defined when vegetation protrudes over the surface)

    ! * diagonal elements for the vegetation canopy (-)
    if(ixCasNrg/=integerMissing) aJac(ixDiag,ixCasNrg) = (dt/canopyDepth)*(-dCanairNetFlux_dCanairTemp) + dMat(ixCasNrg)
    if(ixVegNrg/=integerMissing) aJac(ixDiag,ixVegNrg) = (dt/canopyDepth)*(-dCanopyNetFlux_dCanopyTemp) + dMat(ixVegNrg)
    if(ixVegHyd/=integerMissing) aJac(ixDiag,ixVegHyd) = -scalarFracLiqVeg*(dCanopyEvaporation_dCanLiq - scalarCanopyLiqDeriv)*dt + 1._dp     ! ixVegHyd: CORRECT

    ! * cross-derivative terms w.r.t. canopy water
    if(ixVegHyd/=integerMissing)then
     ! cross-derivative terms w.r.t. system temperatures (kg m-2 K-1)
     if(ixCasNrg/=integerMissing) aJac(ixOffDiag(ixVegHyd,ixCasNrg),ixCasNrg) = -dCanopyEvaporation_dTCanair*dt                                                        ! ixCasNrg: CORRECT
     if(ixVegNrg/=integerMissing) aJac(ixOffDiag(ixVegHyd,ixVegNrg),ixVegNrg) = -dCanopyEvaporation_dTCanopy*dt + dt*scalarCanopyLiqDeriv*dCanLiq_dTcanopy             ! ixVegNrg: CORRECT
     if(ixTopNrg/=integerMissing) aJac(ixOffDiag(ixVegHyd,ixTopNrg),ixTopNrg) = -dCanopyEvaporation_dTGround*dt                                                        ! ixTopNrg: CORRECT
     ! cross-derivative terms w.r.t. canopy water (kg-1 m2)
     if(ixTopHyd/=integerMissing) aJac(ixOffDiag(ixTopHyd,ixVegHyd),ixVegHyd) = (dt/mLayerDepth(1))*(-scalarSoilControl*scalarFracLiqVeg*scalarCanopyLiqDeriv)/iden_water
     ! cross-derivative terms w.r.t. canopy liquid water (J m-1 kg-1)
     ! NOTE: dIce/dLiq = (1 - scalarFracLiqVeg); dIce*LH_fus/canopyDepth = J m-3; dLiq = kg m-2
     if(ixVegNrg/=integerMissing) aJac(ixOffDiag(ixVegNrg,ixVegHyd),ixVegHyd) = (dt/canopyDepth)   *(-dCanopyNetFlux_dCanLiq) - (1._dp - scalarFracLiqVeg)*LH_fus/canopyDepth   ! dF/dLiq
     if(ixTopNrg/=integerMissing) aJac(ixOffDiag(ixTopNrg,ixVegHyd),ixVegHyd) = (dt/mLayerDepth(1))*(-dGroundNetFlux_dCanLiq)
    endif

    ! cross-derivative terms between surface hydrology and the temperature of the vegetation canopy (K-1)
    if(ixVegNrg/=integerMissing)then
     if(ixTopHyd/=integerMissing) aJac(ixOffDiag(ixTopHyd,ixVegNrg),ixVegNrg) = (dt/mLayerDepth(1))*(-scalarSoilControl*scalarCanopyLiqDeriv*dCanLiq_dTcanopy)/iden_water
    endif

    ! cross-derivative terms w.r.t. the temperature of the canopy air space (J m-3 K-1)
    if(ixCasNrg/=integerMissing)then
     if(ixVegNrg/=integerMissing) aJac(ixOffDiag(ixCasNrg,ixVegNrg),ixVegNrg) = (dt/canopyDepth)*(-dCanairNetFlux_dCanopyTemp)
     if(ixTopNrg/=integerMissing) aJac(ixOffDiag(ixCasNrg,ixTopNrg),ixTopNrg) = (dt/canopyDepth)*(-dCanairNetFlux_dGroundTemp)
    endif

    ! cross-derivative terms w.r.t. the temperature of the vegetation canopy (J m-3 K-1)
    if(ixVegNrg/=integerMissing)then
     if(ixCasNrg/=integerMissing) aJac(ixOffDiag(ixVegNrg,ixCasNrg),ixCasNrg) = (dt/canopyDepth)*(-dCanopyNetFlux_dCanairTemp)
     if(ixTopNrg/=integerMissing) aJac(ixOffDiag(ixVegNrg,ixTopNrg),ixTopNrg) = (dt/canopyDepth)*(-dCanopyNetFlux_dGroundTemp)
    endif

    ! cross-derivative terms w.r.t. the temperature of the surface (J m-3 K-1)
    if(ixTopNrg/=integerMissing)then
     if(ixCasNrg/=integerMissing) aJac(ixOffDiag(ixTopNrg,ixCasNrg),ixCasNrg) = (dt/mLayerDepth(1))*(-dGroundNetFlux_dCanairTemp)
     if(ixVegNrg/=integerMissing) aJac(ixOffDiag(ixTopNrg,ixVegNrg),ixVegNrg) = (dt/mLayerDepth(1))*(-dGroundNetFlux_dCanopyTemp)
    endif

   endif  ! if there is a need to compute energy fluxes within vegetation

   ! -----
   ! * energy fluxes for the snow+soil domain...
   ! -------------------------------------------
   if(nSnowSoilNrg>0)then
    do iLayer=1,nLayers  ! loop through all layers in the snow+soil domain

     ! check if the state is in the subset
     if(ixSnowSoilNrg(iLayer)==integerMissing) cycle

     ! - define index within the state subset and the full state vector
     jState = ixSnowSoilNrg(iLayer)        ! index within the state subset

     ! - diagonal elements
     aJac(ixDiag,jState)   = (dt/mLayerDepth(iLayer))*(-dNrgFlux_dTempBelow(iLayer-1) + dNrgFlux_dTempAbove(iLayer)) + dMat(jState)

     ! - lower-diagonal elements
     if(iLayer > 1)then
      if(ixSnowSoilNrg(iLayer-1)/=integerMissing) aJac(ixOffDiag(ixSnowSoilNrg(iLayer-1),jState),jState) = (dt/mLayerDepth(iLayer-1))*( dNrgFlux_dTempBelow(iLayer-1) )
     endif

     ! - upper diagonal elements
     if(iLayer < nLayers)then
      if(ixSnowSoilNrg(iLayer+1)/=integerMissing) aJac(ixOffDiag(ixSnowSoilNrg(iLayer+1),jState),jState) = (dt/mLayerDepth(iLayer+1))*(-dNrgFlux_dTempAbove(iLayer  ) )
     endif

    end do  ! (looping through energy states in the snow+soil domain)
   endif   ! (if the subset includes energy state variables in the snow+soil domain)

   ! -----
   ! * liquid water fluxes for the snow domain...
   ! --------------------------------------------
   if(nSnowOnlyHyd>0)then
    do iLayer=1,nSnow  ! loop through layers in the snow domain

     ! - check that the snow layer is desired
     if(ixSnowOnlyHyd(iLayer)==integerMissing) cycle

     ! - define state indices for the current layer
     watState = ixSnowOnlyHyd(iLayer)   ! hydrology state index within the state subset

     ! compute factor to convert liquid water derivative to total water derivative
     select case( ixHydType(iLayer) )
      case(iname_watLayer); convLiq2tot = mLayerFracLiqSnow(iLayer)
      case default;         convLiq2tot = 1._dp
     end select

     ! - diagonal elements
     aJac(ixDiag,watState) = (dt/mLayerDepth(iLayer))*iLayerLiqFluxSnowDeriv(iLayer)*convLiq2tot + dMat(watState)

     ! - lower-diagonal elements
     if(iLayer > 1)then
      if(ixSnowOnlyHyd(iLayer-1)/=integerMissing) aJac(ixOffDiag(ixSnowOnlyHyd(iLayer-1),watState),watState) = 0._dp  ! sub-diagonal: no dependence on other layers
     endif

     ! - upper diagonal elements
     if(iLayer < nSnow)then
      if(ixSnowOnlyHyd(iLayer+1)/=integerMissing) aJac(ixOffDiag(ixSnowOnlyHyd(iLayer+1),watState),watState) = -(dt/mLayerDepth(iLayer+1))*iLayerLiqFluxSnowDeriv(iLayer)*convLiq2tot       ! dVol(below)/dLiq(above) -- (-)
     endif

     ! - compute cross-derivative terms for energy
     ! NOTE: increase in volumetric liquid water content balanced by a decrease in volumetric ice content
     if(nSnowOnlyNrg>0)then

      ! (define the energy state)
      nrgState = ixSnowOnlyNrg(iLayer)       ! index within the full state vector
      if(nrgstate/=integerMissing)then       ! (energy state for the current layer is within the state subset)

       ! (cross-derivative terms for the current layer)
       aJac(ixOffDiag(nrgState,watState),watState) = -(1._dp - mLayerFracLiqSnow(iLayer))*LH_fus*iden_water     ! (dF/dLiq)
       aJac(ixOffDiag(watState,nrgState),nrgState) = (dt/mLayerDepth(iLayer))*iLayerLiqFluxSnowDeriv(iLayer)*mLayerdTheta_dTk(iLayer)  ! (dVol/dT)

       ! (cross-derivative terms for the layer below)
       if(iLayer < nSnow)then
        aJac(ixOffDiag(ixSnowOnlyHyd(iLayer+1),nrgState),nrgState) = -(dt/mLayerDepth(iLayer+1))*iLayerLiqFluxSnowDeriv(iLayer)*mLayerdTheta_dTk(iLayer)        ! dVol(below)/dT(above) -- K-1
       endif ! (if there is a water state in the layer below the current layer in the given state subset)

      endif ! (if the energy state for the current layer is within the state subset)
     endif ! (if state variables exist for energy in snow+soil layers)

    end do  ! (looping through liquid water states in the snow domain)
   endif   ! (if the subset includes hydrology state variables in the snow domain)

   ! -----
   ! * liquid water fluxes for the soil domain...
   ! --------------------------------------------
   if(nSoilOnlyHyd>0)then
    do iLayer=1,nSoil

     ! - check that the soil layer is desired
     if(ixSoilOnlyHyd(iLayer)==integerMissing) cycle

     ! - define state indices
     watState = ixSoilOnlyHyd(iLayer)         ! hydrology state index within the state subset

     ! - define indices of the soil layers
     jLayer   = iLayer+nSnow                  ! index of layer in the snow+soil vector

     ! - compute the diagonal elements
     ! all terms *excluding* baseflow
     aJac(ixDiag,watState) = (dt/mLayerDepth(jLayer))*(-dq_dHydStateBelow(iLayer-1) + dq_dHydStateAbove(iLayer)) + dMat(watState)

     ! - compute the lower-diagonal elements
     if(iLayer > 1)then
      if(ixSoilOnlyHyd(iLayer-1)/=integerMissing) aJac(ixOffDiag(ixSoilOnlyHyd(iLayer-1),watState),watState) = (dt/mLayerDepth(jLayer-1))*( dq_dHydStateBelow(iLayer-1))
     endif

     ! - compute the upper-diagonal elements
     if(iLayer<nSoil)then
      if(ixSoilOnlyHyd(iLayer+1)/=integerMissing) aJac(ixOffDiag(ixSoilOnlyHyd(iLayer+1),watState),watState) = (dt/mLayerDepth(jLayer+1))*(-dq_dHydStateAbove(iLayer))
     endif

    end do  ! (looping through hydrology states in the soil domain)
   endif   ! (if the subset includes hydrology state variables in the soil domain)

   ! -----
   ! * liquid water fluxes for the aquifer...
   ! ----------------------------------------
   if(ixAqWat/=integerMissing) aJac(ixDiag,ixAqWat) = -dBaseflow_dAquifer*dt + dMat(ixAqWat)

   ! -----
   ! * derivative in liquid water fluxes w.r.t. temperature for the soil domain...
   ! -----------------------------------------------------------------------------
   if(nSoilOnlyHyd>0 .and. nSoilOnlyNrg>0)then
    do iLayer=1,nSoilOnlyHyd

     ! - check that the soil layer is desired
     if(ixSoilOnlyHyd(iLayer)==integerMissing) cycle

     ! - define index of hydrology state variable within the state subset
     watState = ixSoilOnlyHyd(iLayer)

     ! - define indices of the soil layers
     jLayer   = iLayer+nSnow                  ! index of layer in the snow+soil vector

     ! - define the energy state variable
     nrgState = ixNrgLayer(jLayer)       ! index within the full state vector

     ! only compute derivatives if the energy state for the current layer is within the state subset
     if(nrgstate/=integerMissing)then

      ! - compute the Jacobian for the layer itself
      aJac(ixOffDiag(watState,nrgState),nrgState) = (dt/mLayerDepth(jLayer))*(-dq_dNrgStateBelow(iLayer-1) + dq_dNrgStateAbove(iLayer))   ! dVol/dT (K-1) -- flux depends on ice impedance

      ! - include derivatives w.r.t. ground evaporation
      if(nSnow==0 .and. iLayer==1)then  ! upper-most soil layer
       if(computeVegFlux)then
        aJac(ixOffDiag(watState,ixVegHyd),ixVegHyd) = (dt/mLayerDepth(jLayer))*(-dGroundEvaporation_dCanLiq/iden_water)  ! dVol/dLiq (kg m-2)-1
        aJac(ixOffDiag(watState,ixCasNrg),ixCasNrg) = (dt/mLayerDepth(jLayer))*(-dGroundEvaporation_dTCanair/iden_water) ! dVol/dT (K-1)
        aJac(ixOffDiag(watState,ixVegNrg),ixVegNrg) = (dt/mLayerDepth(jLayer))*(-dGroundEvaporation_dTCanopy/iden_water) ! dVol/dT (K-1)
       endif
       aJac(ixOffDiag(watState,ixTopNrg),ixTopNrg) = (dt/mLayerDepth(jLayer))*(-dGroundEvaporation_dTGround/iden_water) + aJac(ixOffDiag(watState,ixTopNrg),ixTopNrg) ! dVol/dT (K-1)
      endif

      ! melt-freeze: compute derivative in energy with respect to mass
      if(mLayerdTheta_dTk(jLayer) > verySmall)then  ! ice is present
       aJac(ixOffDiag(nrgState,watState),watState) = -dVolTot_dPsi0(iLayer)*LH_fus*iden_water    ! dNrg/dMat (J m-3 m-1) -- dMat changes volumetric water, and hence ice content
      else
       aJac(ixOffDiag(nrgState,watState),watState) = 0._dp
      endif

      ! - compute lower diagonal elements
      if(iLayer>1)then
       if(ixSoilOnlyHyd(iLayer-1)/=integerMissing) aJac(ixOffDiag(ixSoilOnlyHyd(iLayer-1),nrgState),nrgState) = (dt/mLayerDepth(jLayer-1))*( dq_dNrgStateBelow(iLayer-1))   ! K-1
      endif

      ! compute upper-diagonal elements
      if(iLayer<nSoil)then
       if(ixSoilOnlyHyd(iLayer+1)/=integerMissing) aJac(ixOffDiag(ixSoilOnlyHyd(iLayer+1),nrgState),nrgState) = (dt/mLayerDepth(jLayer+1))*(-dq_dNrgStateAbove(iLayer))     ! K-1
      endif

     endif   ! (if the energy state for the current layer is within the state subset)

    end do  ! (looping through soil layers)
   endif   ! (if there are state variables for both water and energy in the soil domain)

   if(globalPrintFlag)then
    print*, '** banded analytical Jacobian:'
    write(*,'(a4,1x,100(i17,1x))') 'xCol', (iLayer, iLayer=min(iJac1,nState),min(iJac2,nState))
    do iLayer=kl+1,nBands
     write(*,'(i4,1x,100(e17.10,1x))') iLayer, (aJac(iLayer,jLayer),jLayer=min(iJac1,nState),min(iJac2,nState))
    end do
   end if
   !print*, 'PAUSE: banded analytical Jacobian'; read(*,*)

  ! *********************************************************************************************************************************************************
  ! *********************************************************************************************************************************************************
  ! * PART 2: FULL MATRIX
  ! *********************************************************************************************************************************************************
  ! *********************************************************************************************************************************************************
  case(ixFullMatrix)

   ! check
   if(size(aJac,1)/=size(dMat) .or. size(aJac,2)/=size(dMat))then
    message=trim(message)//'unexpected shape of the Jacobian matrix: expect aJac(nState,nState)'
    err=20; return
   end if

   ! -----
   ! * energy and liquid fluxes over vegetation...
   ! ---------------------------------------------
   if(computeVegFlux)then  ! (derivatives only defined when vegetation protrudes over the surface)

    ! * liquid water fluxes for vegetation canopy (-)
    if(ixVegHyd/=integerMissing) aJac(ixVegHyd,ixVegHyd) = -scalarFracLiqVeg*(dCanopyEvaporation_dCanLiq - scalarCanopyLiqDeriv)*dt + 1._dp

    ! * cross-derivative terms for canopy water
    if(ixVegHyd/=integerMissing)then
     ! cross-derivative terms w.r.t. system temperatures (kg m-2 K-1)
     if(ixCasNrg/=integerMissing) aJac(ixVegHyd,ixCasNrg) = -dCanopyEvaporation_dTCanair*dt
     if(ixVegNrg/=integerMissing) aJac(ixVegHyd,ixVegNrg) = -dCanopyEvaporation_dTCanopy*dt + dt*scalarCanopyLiqDeriv*dCanLiq_dTcanopy
     if(ixTopNrg/=integerMissing) aJac(ixVegHyd,ixTopNrg) = -dCanopyEvaporation_dTGround*dt
     ! cross-derivative terms w.r.t. canopy water (kg-1 m2)
     if(ixTopHyd/=integerMissing) aJac(ixTopHyd,ixVegHyd) = (dt/mLayerDepth(1))*(-scalarSoilControl*scalarFracLiqVeg*scalarCanopyLiqDeriv)/iden_water
     ! cross-derivative terms w.r.t. canopy liquid water (J m-1 kg-1)
     ! NOTE: dIce/dLiq = (1 - scalarFracLiqVeg); dIce*LH_fus/canopyDepth = J m-3; dLiq = kg m-2
     if(ixVegNrg/=integerMissing) aJac(ixVegNrg,ixVegHyd) = (dt/canopyDepth)   *(-dCanopyNetFlux_dCanLiq) - (1._dp - scalarFracLiqVeg)*LH_fus/canopyDepth   ! dF/dLiq
     if(ixTopNrg/=integerMissing) aJac(ixTopNrg,ixVegHyd) = (dt/mLayerDepth(1))*(-dGroundNetFlux_dCanLiq)
    endif

    ! cross-derivative terms w.r.t. canopy temperature (K-1)
    if(ixVegNrg/=integerMissing)then
     if(ixTopHyd/=integerMissing) aJac(ixTopHyd,ixVegNrg) = (dt/mLayerDepth(1))*(-scalarSoilControl*scalarCanopyLiqDeriv*dCanLiq_dTcanopy)/iden_water
    endif

    ! energy fluxes with the canopy air space (J m-3 K-1)
    if(ixCasNrg/=integerMissing)then
                                  aJac(ixCasNrg,ixCasNrg) = (dt/canopyDepth)*(-dCanairNetFlux_dCanairTemp) + dMat(ixCasNrg)
     if(ixVegNrg/=integerMissing) aJac(ixCasNrg,ixVegNrg) = (dt/canopyDepth)*(-dCanairNetFlux_dCanopyTemp)
     if(ixTopNrg/=integerMissing) aJac(ixCasNrg,ixTopNrg) = (dt/canopyDepth)*(-dCanairNetFlux_dGroundTemp)
    endif

    ! energy fluxes with the vegetation canopy (J m-3 K-1)
    if(ixVegNrg/=integerMissing)then
     if(ixCasNrg/=integerMissing) aJac(ixVegNrg,ixCasNrg) = (dt/canopyDepth)*(-dCanopyNetFlux_dCanairTemp)
                                  aJac(ixVegNrg,ixVegNrg) = (dt/canopyDepth)*(-dCanopyNetFlux_dCanopyTemp) + dMat(ixVegNrg)
     if(ixTopNrg/=integerMissing) aJac(ixVegNrg,ixTopNrg) = (dt/canopyDepth)*(-dCanopyNetFlux_dGroundTemp)
    endif

    ! energy fluxes with the surface (J m-3 K-1)
    if(ixTopNrg/=integerMissing)then
     if(ixCasNrg/=integerMissing) aJac(ixTopNrg,ixCasNrg) = (dt/mLayerDepth(1))*(-dGroundNetFlux_dCanairTemp)
     if(ixVegNrg/=integerMissing) aJac(ixTopNrg,ixVegNrg) = (dt/mLayerDepth(1))*(-dGroundNetFlux_dCanopyTemp)
    endif

   endif  ! if there is a need to compute energy fluxes within vegetation

   ! -----
   ! * energy fluxes for the snow+soil domain...
   ! -------------------------------------------
   if(nSnowSoilNrg>0)then
    do iLayer=1,nLayers  ! loop through all layers in the snow+soil domain

     ! check if the state is in the subset
     if(ixSnowSoilNrg(iLayer)==integerMissing) cycle

     ! - define index within the state subset and the full state vector
     jState = ixSnowSoilNrg(iLayer)        ! index within the state subset

     ! - diagonal elements
     aJac(jState,jState)   = (dt/mLayerDepth(iLayer))*(-dNrgFlux_dTempBelow(iLayer-1) + dNrgFlux_dTempAbove(iLayer)) + dMat(jState)

     ! - lower-diagonal elements
     if(iLayer > 1)then
      if(ixSnowSoilNrg(iLayer-1)/=integerMissing) aJac(ixSnowSoilNrg(iLayer-1),jState) = (dt/mLayerDepth(iLayer-1))*( dNrgFlux_dTempBelow(iLayer-1) )
     endif

     ! - upper diagonal elements
     if(iLayer < nLayers)then
      if(ixSnowSoilNrg(iLayer+1)/=integerMissing) aJac(ixSnowSoilNrg(iLayer+1),jState) = (dt/mLayerDepth(iLayer+1))*(-dNrgFlux_dTempAbove(iLayer  ) )
     endif

    end do  ! (looping through energy states in the snow+soil domain)
   endif   ! (if the subset includes energy state variables in the snow+soil domain)

   ! -----
   ! * liquid water fluxes for the snow domain...
   ! --------------------------------------------
   if(nSnowOnlyHyd>0)then
    do iLayer=1,nSnow  ! loop through layers in the snow domain

     ! - check that the snow layer is desired
     if(ixSnowOnlyHyd(iLayer)==integerMissing) cycle

     ! - define state indices for the current layer
     watState = ixSnowOnlyHyd(iLayer)   ! hydrology state index within the state subset

     ! compute factor to convert liquid water derivative to total water derivative
     select case( ixHydType(iLayer) )
      case(iname_watLayer); convLiq2tot = mLayerFracLiqSnow(iLayer)
      case default;         convLiq2tot = 1._dp
     end select

     ! - diagonal elements
     aJac(watState,watState) = (dt/mLayerDepth(iLayer))*iLayerLiqFluxSnowDeriv(iLayer)*convLiq2tot + dMat(watState)

     ! - lower-diagonal elements
     if(iLayer > 1)then
      if(ixSnowOnlyHyd(iLayer-1)/=integerMissing) aJac(ixSnowOnlyHyd(iLayer-1),watState) = 0._dp  ! sub-diagonal: no dependence on other layers
     endif

     ! - upper diagonal elements
     if(iLayer < nSnow)then
      if(ixSnowOnlyHyd(iLayer+1)/=integerMissing) aJac(ixSnowOnlyHyd(iLayer+1),watState) = -(dt/mLayerDepth(iLayer+1))*iLayerLiqFluxSnowDeriv(iLayer)*convLiq2tot       ! dVol(below)/dLiq(above) -- (-)
     endif

     ! - compute cross-derivative terms for energy
     ! NOTE: increase in volumetric liquid water content balanced by a decrease in volumetric ice content
     if(nSnowOnlyNrg>0)then

      ! (define the energy state)
      nrgState = ixSnowOnlyNrg(iLayer)       ! index within the full state vector
      if(nrgstate/=integerMissing)then       ! (energy state for the current layer is within the state subset)

       ! (cross-derivative terms for the current layer)
       aJac(nrgState,watState) = -(1._dp - mLayerFracLiqSnow(iLayer))*LH_fus*iden_water     ! (dF/dLiq)
       aJac(watState,nrgState) = (dt/mLayerDepth(iLayer))*iLayerLiqFluxSnowDeriv(iLayer)*mLayerdTheta_dTk(iLayer)  ! (dVol/dT)

       ! (cross-derivative terms for the layer below)
       if(iLayer < nSnow)then
        aJac(ixSnowOnlyHyd(iLayer+1),nrgState) = -(dt/mLayerDepth(iLayer+1))*iLayerLiqFluxSnowDeriv(iLayer)*mLayerdTheta_dTk(iLayer)        ! dVol(below)/dT(above) -- K-1
       endif ! (if there is a water state in the layer below the current layer in the given state subset)

      endif ! (if the energy state for the current layer is within the state subset)
     endif ! (if state variables exist for energy in snow+soil layers)

    end do  ! (looping through liquid water states in the snow domain)
   endif   ! (if the subset includes hydrology state variables in the snow domain)

   ! -----
   ! * liquid water fluxes for the soil domain...
   ! --------------------------------------------
   if(nSoilOnlyHyd>0)then

    do iLayer=1,nSoil

     ! - check that the soil layer is desired
     if(ixSoilOnlyHyd(iLayer)==integerMissing) cycle

     ! - define state indices
     watState = ixSoilOnlyHyd(iLayer)         ! hydrology state index within the state subset

     ! - define indices of the soil layers
     jLayer   = iLayer+nSnow                  ! index of layer in the snow+soil vector

     ! - compute the diagonal elements
     ! all terms *excluding* baseflow
     aJac(watState,watState) = (dt/mLayerDepth(jLayer))*(-dq_dHydStateBelow(iLayer-1) + dq_dHydStateAbove(iLayer)) + dMat(watState)

     ! - compute the lower-diagonal elements
     if(iLayer > 1)then
      if(ixSoilOnlyHyd(iLayer-1)/=integerMissing) aJac(ixSoilOnlyHyd(iLayer-1),watState) = (dt/mLayerDepth(jLayer-1))*( dq_dHydStateBelow(iLayer-1))
     endif

     ! - compute the upper-diagonal elements
     if(iLayer<nSoil)then
      if(ixSoilOnlyHyd(iLayer+1)/=integerMissing) aJac(ixSoilOnlyHyd(iLayer+1),watState) = (dt/mLayerDepth(jLayer+1))*(-dq_dHydStateAbove(iLayer))
     endif

     ! - include terms for baseflow
     if(computeBaseflow .and. nSoilOnlyHyd==nSoil)then
      do pLayer=1,nSoil
       qState = ixSoilOnlyHyd(pLayer)  ! hydrology state index within the state subset
       aJac(watState,qState) = aJac(watState,qState) + (dt/mLayerDepth(jLayer))*dBaseflow_dMatric(iLayer,pLayer)
      end do
     endif

    end do  ! (looping through hydrology states in the soil domain)
   endif   ! (if the subset includes hydrology state variables in the soil domain)

   ! -----
   ! * liquid water fluxes for the aquifer...
   ! ----------------------------------------
   if(ixAqWat/=integerMissing) aJac(ixAqWat,ixAqWat) = -dBaseflow_dAquifer*dt + dMat(ixAqWat)

   ! -----
   ! * derivative in liquid water fluxes w.r.t. temperature for the soil domain...
   ! -----------------------------------------------------------------------------
   if(nSoilOnlyHyd>0 .and. nSoilOnlyNrg>0)then
    do iLayer=1,nSoilOnlyHyd

     ! - check that the soil layer is desired
     if(ixSoilOnlyHyd(iLayer)==integerMissing) cycle

     ! - define index of hydrology state variable within the state subset
     watState = ixSoilOnlyHyd(iLayer)

     ! - define indices of the soil layers
     jLayer   = iLayer+nSnow                  ! index of layer in the snow+soil vector

     ! - define the energy state variable
     nrgState = ixNrgLayer(jLayer)       ! index within the full state vector

     ! only compute derivatives if the energy state for the current layer is within the state subset
     if(nrgstate/=integerMissing)then

      ! - compute the Jacobian for the layer itself
      aJac(watState,nrgState) = (dt/mLayerDepth(jLayer))*(-dq_dNrgStateBelow(iLayer-1) + dq_dNrgStateAbove(iLayer))   ! dVol/dT (K-1) -- flux depends on ice impedance

      ! - include derivatives w.r.t. ground evaporation
      if(nSnow==0 .and. iLayer==1)then  ! upper-most soil layer
       if(computeVegFlux)then
        aJac(watState,ixVegHyd) = (dt/mLayerDepth(jLayer))*(-dGroundEvaporation_dCanLiq/iden_water)  ! dVol/dLiq (kg m-2)-1
        aJac(watState,ixCasNrg) = (dt/mLayerDepth(jLayer))*(-dGroundEvaporation_dTCanair/iden_water) ! dVol/dT (K-1)
        aJac(watState,ixVegNrg) = (dt/mLayerDepth(jLayer))*(-dGroundEvaporation_dTCanopy/iden_water) ! dVol/dT (K-1)
       endif
       aJac(watState,ixTopNrg) = (dt/mLayerDepth(jLayer))*(-dGroundEvaporation_dTGround/iden_water) + aJac(watState,ixTopNrg) ! dVol/dT (K-1)
      endif

      ! melt-freeze: compute derivative in energy with respect to mass
      if(mLayerdTheta_dTk(jLayer) > verySmall)then  ! ice is present
       aJac(nrgState,watState) = -dVolTot_dPsi0(iLayer)*LH_fus*iden_water    ! dNrg/dMat (J m-3 m-1) -- dMat changes volumetric water, and hence ice content
      else
       aJac(nrgState,watState) = 0._dp
      endif

      ! - compute lower diagonal elements
      if(iLayer>1)then
       if(ixSoilOnlyHyd(iLayer-1)/=integerMissing) aJac(ixSoilOnlyHyd(iLayer-1),nrgState) = (dt/mLayerDepth(jLayer-1))*( dq_dNrgStateBelow(iLayer-1))   ! K-1
      endif

      ! compute upper-diagonal elements
      if(iLayer<nSoil)then
       if(ixSoilOnlyHyd(iLayer+1)/=integerMissing) aJac(ixSoilOnlyHyd(iLayer+1),nrgState) = (dt/mLayerDepth(jLayer+1))*(-dq_dNrgStateAbove(iLayer))     ! K-1
      endif

     endif   ! (if the energy state for the current layer is within the state subset)

    end do  ! (looping through soil layers)
   endif   ! (if there are state variables for both water and energy in the soil domain)

   ! print the Jacobian
   if(globalPrintFlag)then
    print*, '** analytical Jacobian (full):'
    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, aJac(min(iJac1,nState):min(iJac2,nState),iLayer)
    end do
   end if

  ! ***
  ! check
  case default; err=20; message=trim(message)//'unable to identify option for the type of matrix'; return

 end select  ! type of matrix

 ! end association to variables in the data structures
 end associate

 end subroutine computJacob


 ! **********************************************************************************************************
 ! private function: get the off-diagonal index in the band-diagonal matrix
 ! **********************************************************************************************************
 function ixOffDiag(jState,iState)
 implicit none
 integer(i4b),intent(in)  :: jState    ! off-diagonal state
 integer(i4b),intent(in)  :: iState    ! diagonal state
 integer(i4b)             :: ixOffDiag ! off-diagonal index in gthe band-diagonal matrix
 ixOffDiag = ixBandOffset + jState - iState
 end function ixOffDiag

end module computJacob_module