!
! CRTM_Adjoint_Module
!
! Module containing the CRTM adjoint model function.
!
!
! CREATION HISTORY:
!       Written by:     Paul van Delst, 28-Jan-2005
!                       paul.vandelst@noaa.gov
!

MODULE CRTM_Adjoint_Module


  ! ------------
  ! Module usage
  ! ------------
  USE Type_Kinds,                 ONLY: fp
  USE Message_Handler,            ONLY: SUCCESS, FAILURE, WARNING, Display_Message
  USE CRTM_Parameters,            ONLY: SET, NOT_SET, ZERO, ONE, &
                                        MAX_N_LAYERS           , &
                                        MAX_N_PHASE_ELEMENTS   , &
                                        MAX_N_LEGENDRE_TERMS   , &
                                        MAX_N_STOKES           , &
                                        MAX_N_ANGLES           , &
                                        MAX_N_AZIMUTH_FOURIER  , &
                                        MAX_SOURCE_ZENITH_ANGLE
  USE CRTM_SpcCoeff,              ONLY: SC, VISIBLE_SENSOR
  USE CRTM_Atmosphere_Define,     ONLY: CRTM_Atmosphere_type, &
                                        CRTM_Atmosphere_Destroy, &
                                        CRTM_Atmosphere_IsValid, &
                                        CRTM_Atmosphere_AddLayerCopy
  USE CRTM_Surface_Define,        ONLY: CRTM_Surface_type, &
                                        CRTM_Surface_IsValid
  USE CRTM_Geometry_Define,       ONLY: CRTM_Geometry_type, &
                                        CRTM_Geometry_IsValid
  USE CRTM_ChannelInfo_Define,    ONLY: CRTM_ChannelInfo_type
  USE CRTM_Options_Define,        ONLY: CRTM_Options_type, &
                                        CRTM_Options_IsValid
  USE CRTM_Atmosphere,            ONLY: CRTM_Atmosphere_AddLayers, &
                                        CRTM_Atmosphere_AddLayers_AD
  USE CRTM_GeometryInfo_Define,   ONLY: CRTM_GeometryInfo_type, &
                                        CRTM_GeometryInfo_SetValue, &
                                        CRTM_GeometryInfo_GetValue
  USE CRTM_GeometryInfo,          ONLY: CRTM_GeometryInfo_Compute
  USE CRTM_AtmAbsorption,         ONLY: CRTM_AAVariables_type        , &
                                        CRTM_Compute_AtmAbsorption, &
                                        CRTM_Compute_AtmAbsorption_AD, &
                                        CRTM_Destroy_Predictor     , &
                                        CRTM_Allocate_Predictor    , &
                                        CRTM_Compute_Predictors    , &
                                        CRTM_Compute_Predictors_AD , &
                                        CRTM_Predictor_type        , &
                                        CRTM_APVariables_type    

! **** REPLACE
  USE CRTM_AtmScatter_Define,     ONLY: CRTM_AtmOptics_type     => CRTM_AtmScatter_type    , &
                                        CRTM_Allocate_AtmOptics => CRTM_Allocate_AtmScatter, &
                                        CRTM_Destroy_AtmOptics  => CRTM_Destroy_AtmScatter
! **** WITH THE FOLLOWING
!  USE CRTM_AtmOptics_Define,      ONLY: CRTM_AtmOptics_type    , &
!                                        CRTM_Allocate_AtmOptics, &
!                                        CRTM_Destroy_AtmOptics 
! ****

  USE CRTM_AerosolScatter,        ONLY: CRTM_Compute_AerosolScatter   , &
                                        CRTM_Compute_AerosolScatter_AD
  USE CRTM_CloudScatter,          ONLY: CRTM_Compute_CloudScatter   , &
                                        CRTM_Compute_CloudScatter_AD
  USE CRTM_AtmOptics,             ONLY: CRTM_AOVariables_type    , &
                                        CRTM_Combine_AtmOptics   , &
                                        CRTM_Combine_AtmOptics_AD
  USE CRTM_SfcOptics,             ONLY: CRTM_SfcOptics_type     , &
                                        CRTM_Allocate_SfcOptics , &
                                        CRTM_Destroy_SfcOptics  , &
                                        CRTM_Compute_SurfaceT   , &
                                        CRTM_Compute_SurfaceT_AD
  USE CRTM_RTSolution,            ONLY: CRTM_RTSolution_type      , &
                                        CRTM_Compute_nStreams     , &
                                        CRTM_Compute_RTSolution   , &
                                        CRTM_Compute_RTSolution_AD
  USE RTV_Define,                 ONLY: RTV_type      , &
                                        RTV_Associated, &
                                        RTV_Destroy   , &
                                        RTV_Create
  USE CRTM_AntCorr,               ONLY: CRTM_Compute_AntCorr, &
                                        CRTM_Compute_AntCorr_AD
  USE CRTM_MoleculeScatter,       ONLY: CRTM_Compute_MoleculeScatter, &
                                        CRTM_Compute_MoleculeScatter_AD
  USE CRTM_AncillaryInput_Define, ONLY: CRTM_AncillaryInput_type

  USE CRTM_CloudCoeff,            ONLY: CRTM_CloudCoeff_IsLoaded
  USE CRTM_AerosolCoeff,          ONLY: CRTM_AerosolCoeff_IsLoaded

  
  ! Internal variable definition modules
  ! ...CloudScatter
  USE CSvar_Define, ONLY: CSvar_type, &
                          CSvar_Associated, &
                          CSvar_Destroy   , &
                          CSvar_Create    
  ! ...AerosolScatter
  USE ASvar_Define, ONLY: ASvar_type, &
                          ASvar_Associated, &
                          ASvar_Destroy   , &
                          ASvar_Create    


  ! -----------------------
  ! Disable implicit typing
  ! -----------------------
  IMPLICIT NONE


  ! ------------
  ! Visibilities
  ! ------------
  ! Everything private by default
  PRIVATE
  ! Public procedures
  PUBLIC :: CRTM_Adjoint
  PUBLIC :: CRTM_Adjoint_Version


  ! -----------------
  ! Module parameters
  ! -----------------
  ! Version Id for the module
  CHARACTER(*), PARAMETER :: MODULE_VERSION_ID = &
  '$Id: CRTM_Adjoint_Module.f90 22707 2012-11-21 21:09:10Z paul.vandelst@noaa.gov $'


CONTAINS


!--------------------------------------------------------------------------------
!:sdoc+:
!
! NAME:
!       CRTM_Adjoint
!
! PURPOSE:
!       Function that calculates the adjoint of top-of-atmosphere (TOA)
!       radiances and brightness temperatures for an input atmospheric
!       profile or profile set and user specified satellites/channels.
!
! CALLING SEQUENCE:
!       Error_Status = CRTM_Adjoint( Atmosphere       , &
!                                    Surface          , &
!                                    RTSolution_AD    , &
!                                    Geometry         , &
!                                    ChannelInfo      , &
!                                    Atmosphere_AD    , &
!                                    Surface_AD       , &
!                                    RTSolution       , &
!                                    Options = Options  )
!
! INPUTS:
!       Atmosphere:     Structure containing the Atmosphere data.
!                       UNITS:      N/A
!                       TYPE:       CRTM_Atmosphere_type
!                       DIMENSION:  Rank-1 (n_Profiles)
!                       ATTRIBUTES: INTENT(IN)
!
!       Surface:        Structure containing the Surface data.
!                       UNITS:      N/A
!                       TYPE:       CRTM_Surface_type
!                       DIMENSION:  Same as input Atmosphere structure
!                       ATTRIBUTES: INTENT(IN)
!
!       RTSolution_AD:  Structure containing the RT solution adjoint inputs.
!                       **NOTE: On EXIT from this function, the contents of
!                               this structure may be modified (e.g. set to
!                               zero.)
!                       UNITS:      N/A
!                       TYPE:       CRTM_RTSolution_type
!                       DIMENSION:  Rank-2 (n_Channels x n_Profiles)
!                       ATTRIBUTES: INTENT(IN OUT)
!
!       Geometry:       Structure containing the view geometry
!                       information.
!                       UNITS:      N/A
!                       TYPE:       CRTM_Geometry_type
!                       DIMENSION:  Same as input Atmosphere argument
!                       ATTRIBUTES: INTENT(IN)
!
!       ChannelInfo:    Structure returned from the CRTM_Init() function
!                       that contains the satellite/sensor channel index
!                       information.
!                       UNITS:      N/A
!                       TYPE:       CRTM_ChannelInfo_type
!                       DIMENSION:  Rank-1 (n_Sensors)
!                       ATTRIBUTES: INTENT(IN)
!
! OPTIONAL INPUTS:
!       Options:        Options structure containing the optional forward model
!                       arguments for the CRTM.
!                       UNITS:      N/A
!                       TYPE:       CRTM_Options_type
!                       DIMENSION:  Same as input Atmosphere structure
!                       ATTRIBUTES: INTENT(IN), OPTIONAL
!
! OUTPUTS:
!       Atmosphere_AD:  Structure containing the adjoint Atmosphere data.
!                       **NOTE: On ENTRY to this function, the contents of
!                               this structure should be defined (e.g.
!                               initialized to some value based on the
!                               position of this function in the call chain.)
!                       UNITS:      N/A
!                       TYPE:       CRTM_Atmosphere_type
!                       DIMENSION:  Same as input Atmosphere argument
!                       ATTRIBUTES: INTENT(IN OUT)
!
!       Surface_AD:     Structure containing the tangent-linear Surface data.
!                       **NOTE: On ENTRY to this function, the contents of
!                               this structure should be defined (e.g.
!                               initialized to some value based on the
!                               position of this function in the call chain.)
!                       UNITS:      N/A
!                       TYPE:       CRTM_Surface_type
!                       DIMENSION:  Same as input Atmosphere argument
!                       ATTRIBUTES: INTENT(IN OUT)
!
!       RTSolution:     Structure containing the solution to the RT equation
!                       for the given inputs.
!                       UNITS:      N/A
!                       TYPE:       CRTM_RTSolution_type
!                       DIMENSION:  Same as input RTSolution_AD argument
!                       ATTRIBUTES: INTENT(IN OUT)
!
! FUNCTION RESULT:
!       Error_Status:   The return value is an integer defining the error status.
!                       The error codes are defined in the Message_Handler module.
!                       If == SUCCESS the computation was sucessful
!                          == FAILURE an unrecoverable error occurred
!                       UNITS:      N/A
!                       TYPE:       INTEGER
!                       DIMENSION:  Scalar
!
! SIDE EFFECTS:
!      Note that the input adjoint arguments are modified upon exit, and
!      the output adjoint arguments must be defined upon entry. This is
!      a consequence of the adjoint formulation where, effectively, the
!      chain rule is being used and this function could reside anywhere
!      in the chain of derivative terms.
!
! COMMENTS:
!       - The Options optional structure arguments contain
!         spectral information (e.g. emissivity) that must have the same
!         spectral dimensionality (the "L" dimension) as the RTSolution
!         structures.
!
!       - The INTENT on the output RTSolution, Atmosphere_AD, and
!         Surface_AD arguments are IN OUT rather than just OUT. This is
!         necessary because the arguments should be defined upon input.
!         To prevent memory leaks, the IN OUT INTENT is a must.
!
!:sdoc-:
!--------------------------------------------------------------------------------

  FUNCTION CRTM_Adjoint( &
    Atmosphere   , &  ! FWD Input, M
    Surface      , &  ! FWD Input, M
    RTSolution_AD, &  ! AD  Input, L x M   
    Geometry     , &  ! Input, M
    ChannelInfo  , &  ! Input, Scalar  
    Atmosphere_AD, &  ! AD  Output, M
    Surface_AD   , &  ! AD  Output, M
    RTSolution   , &  ! FWD Output, L x M
    Options      ) &  ! Optional FWD input,  M
  RESULT( Error_Status )
    ! Arguments
    TYPE(CRTM_Atmosphere_type)       , INTENT(IN)     :: Atmosphere(:)      ! M
    TYPE(CRTM_Surface_type)          , INTENT(IN)     :: Surface(:)         ! M
    TYPE(CRTM_RTSolution_type)       , INTENT(IN OUT) :: RTSolution_AD(:,:) ! L x M
    TYPE(CRTM_Geometry_type)         , INTENT(IN)     :: Geometry(:)        ! M
    TYPE(CRTM_ChannelInfo_type)      , INTENT(IN)     :: ChannelInfo(:)     ! n_Sensors
    TYPE(CRTM_Atmosphere_type)       , INTENT(IN OUT) :: Atmosphere_AD(:)   ! M
    TYPE(CRTM_Surface_type)          , INTENT(IN OUT) :: Surface_AD(:)      ! M
    TYPE(CRTM_RTSolution_type)       , INTENT(IN OUT) :: RTSolution(:,:)    ! L x M
    TYPE(CRTM_Options_type), OPTIONAL, INTENT(IN)     :: Options(:)         ! M
    ! Function result
    INTEGER :: Error_Status
    ! Local parameters
    CHARACTER(*), PARAMETER :: ROUTINE_NAME = 'CRTM_Adjoint'
    ! Local variables
    CHARACTER(256) :: Message
    LOGICAL :: Options_Present
    LOGICAL :: Check_Input
    LOGICAL :: User_Emissivity, User_Direct_Reflectivity
    LOGICAL :: User_AntCorr, Compute_AntCorr
    LOGICAL :: Atmosphere_Invalid, Surface_Invalid, Geometry_Invalid, Options_Invalid
    INTEGER :: iFOV
    INTEGER :: n, n_Sensors,  SensorIndex
    INTEGER :: l, n_Channels, ChannelIndex
    INTEGER :: m, n_Profiles
    INTEGER :: j, ln
    INTEGER :: n_Full_Streams, mth_Azi
    INTEGER :: AllocStatus(2), AllocStatus_AD(2)
    REAL(fp) :: Source_ZA
    REAL(fp) :: Wavenumber
    ! Local ancillary input structure
    TYPE(CRTM_AncillaryInput_type) :: AncillaryInput
    ! Local atmosphere structure for extra layering
    TYPE(CRTM_Atmosphere_type) :: Atm, Atm_AD
    ! Component variables
    TYPE(CRTM_GeometryInfo_type) :: GeometryInfo
    TYPE(CRTM_Predictor_type)    :: Predictor, Predictor_AD
    TYPE(CRTM_AtmOptics_type)    :: AtmOptics, AtmOptics_AD 
    TYPE(CRTM_SfcOPtics_type)    :: SfcOptics, SfcOptics_AD
    ! Component variable internals
    TYPE(CRTM_APVariables_type) :: APV  ! Predictor
    TYPE(CRTM_AAVariables_type) :: AAV  ! AtmAbsorption
    TYPE(CSVar_type) :: CSvar  ! CloudScatter
    TYPE(ASVar_type) :: ASvar  ! AerosolScatter
    TYPE(CRTM_AOVariables_type) :: AOV  ! AtmOptics
    TYPE(RTV_type) :: RTV  ! RTSolution

    ! ------
    ! Set up
    ! ------
    Error_Status = SUCCESS


    ! ----------------------------------------
    ! If no sensors or channels, simply return
    ! ----------------------------------------
    n_Sensors  = SIZE(ChannelInfo)
    n_Channels = SUM(ChannelInfo%n_Channels)
    IF ( n_Sensors == 0 .OR. n_Channels == 0 ) RETURN


    ! ---------------------------
    ! RTSolution arrays too small
    ! ---------------------------
    IF ( SIZE(RTSolution   ,DIM=1) < n_Channels .OR. &
         SIZE(RTSolution_AD,DIM=1) < n_Channels      ) THEN
      Error_Status = FAILURE
      WRITE( Message,'("Output RTSolution structure arrays too small (",i0," and ",i0,&
             &") to hold results for the number of requested channels (",i0,")")') &
             SIZE(RTSolution,DIM=1), SIZE(RTSolution_AD,DIM=1), n_Channels
      CALL Display_Message( ROUTINE_NAME, TRIM(Message), Error_Status )
      RETURN
    END IF


    ! ----------------------------
    ! Check the number of profiles
    ! ----------------------------
    ! Number of atmospheric profiles.
    n_Profiles = SIZE(Atmosphere)

    ! Check the profile dimensionality
    ! of the other mandatory arguments
    IF ( SIZE(Surface)             /= n_Profiles .OR. &
         SIZE(RTSolution_AD,DIM=2) /= n_Profiles .OR. &
         SIZE(Geometry)            /= n_Profiles .OR. &
         SIZE(Atmosphere_AD)       /= n_Profiles .OR. &
         SIZE(Surface_AD)          /= n_Profiles .OR. &
         SIZE(RTSolution,   DIM=2) /= n_Profiles      ) THEN
      Error_Status = FAILURE
      Message = 'Inconsistent profile dimensionality for input arguments.'
      CALL Display_Message( ROUTINE_NAME, TRIM(Message), Error_Status )
      RETURN
    END IF

    ! Check the profile dimensionality
    ! of the other optional arguments
    Options_Present = .FALSE.
    IF ( PRESENT(Options) ) THEN
      Options_Present = .TRUE.
      IF ( SIZE(Options) /= n_Profiles ) THEN
        Error_Status = FAILURE
        Message = 'Inconsistent profile dimensionality for Options optional input argument.'
        CALL Display_Message( ROUTINE_NAME, TRIM(Message), Error_Status )
        RETURN
      END IF
    END IF

 

    !#--------------------------------------------------------------------------#
    !#                           -- PROFILE LOOP --                             #
    !#--------------------------------------------------------------------------#
    Profile_Loop: DO m = 1, n_Profiles

      ! Check the cloud and aerosol coeff. data for cases with clouds and aerosol
      IF( Atmosphere(m)%n_Clouds > 0 .AND. .NOT. CRTM_CloudCoeff_IsLoaded() )THEN
         Error_Status = FAILURE                                                               
         WRITE( Message,'("The CloudCoeff data must be loaded (with CRTM_Init routine) ", &   
                &"for the cloudy case profile #",i0)' ) m                                     
         CALL Display_Message( ROUTINE_NAME, Message, Error_Status )                    
         RETURN                                                                               
      END IF
      IF( Atmosphere(m)%n_Aerosols > 0 .AND. .NOT. CRTM_AerosolCoeff_IsLoaded() )THEN
         Error_Status = FAILURE                                                                 
         WRITE( Message,'("The AerosolCoeff data must be loaded (with CRTM_Init routine) ", &   
                &"for the aerosol case profile #",i0)' ) m                                      
         CALL Display_Message( ROUTINE_NAME, TRIM(Message), Error_Status )                      
         RETURN                                                                                 
      END IF

      ! Copy over forward "non-variable" inputs to adjoint outputs
      ! ...Atmosphere
      Atmosphere_AD(m)%Climatology = Atmosphere(m)%Climatology
      DO j = 1, Atmosphere(m)%n_Absorbers
        Atmosphere_AD(m)%Absorber_ID(j)    = Atmosphere(m)%Absorber_ID(j)
        Atmosphere_AD(m)%Absorber_Units(j) = Atmosphere(m)%Absorber_Units(j)
      END DO
      ! ...Surface
      Surface_AD(m)%Land_Coverage  = Surface(m)%Land_Coverage 
      Surface_AD(m)%Water_Coverage = Surface(m)%Water_Coverage
      Surface_AD(m)%Snow_Coverage  = Surface(m)%Snow_Coverage 
      Surface_AD(m)%Ice_Coverage   = Surface(m)%Ice_Coverage  
      Surface_AD(m)%Land_Type  = Surface(m)%Land_Type 
      Surface_AD(m)%Water_Type = Surface(m)%Water_Type
      Surface_AD(m)%Snow_Type  = Surface(m)%Snow_Type 
      Surface_AD(m)%Ice_Type   = Surface(m)%Ice_Type  


      ! Check the optional Options structure argument
      ! ...Specify default actions
      Check_Input     = .TRUE.
      User_Emissivity = .FALSE.
      User_AntCorr    = .FALSE.
      ! ...Check the Options argument
      IF (Options_Present) THEN
        ! Override input checker with option
        Check_Input = Options(m)%Check_Input
        ! Check if the supplied emissivity should be used
        User_Emissivity = Options(m)%Use_Emissivity
        IF ( Options(m)%Use_Emissivity ) THEN
          ! Are the channel dimensions consistent
          IF ( Options(m)%n_Channels < n_Channels ) THEN
            Error_Status = FAILURE
            WRITE( Message,'( "Input Options channel dimension (", i0, ") is less ", &
                   &"than the number of requested channels (",i0, ")" )' ) &
                   Options(m)%n_Channels, n_Channels
            CALL Display_Message( ROUTINE_NAME, TRIM(Message), Error_Status )
            RETURN
          END IF
          ! Check if the supplied direct reflectivity should be used
          User_Direct_Reflectivity = Options(m)%Use_Direct_Reflectivity
        END IF
        ! Check if antenna correction should be attempted
        User_AntCorr = Options(m)%Use_Antenna_Correction
        ! Copy over ancillary input
        AncillaryInput%SSU    = Options(m)%SSU
        AncillaryInput%Zeeman = Options(m)%Zeeman
      END IF


      ! Check the input data if required
      IF ( Check_Input ) THEN
        ! ...Mandatory inputs
        Atmosphere_Invalid = .NOT. CRTM_Atmosphere_IsValid( Atmosphere(m) )
        Surface_Invalid    = .NOT. CRTM_Surface_IsValid( Surface(m) )
        Geometry_Invalid   = .NOT. CRTM_Geometry_IsValid( Geometry(m) )
        IF ( Atmosphere_Invalid .OR. Surface_Invalid .OR. Geometry_Invalid ) THEN
          Error_Status = FAILURE
          WRITE( Message,'("Input data check failed for profile #",i0)' ) m
          CALL Display_Message( ROUTINE_NAME, Message, Error_Status )
          RETURN
        END IF
        ! ...Optional input
        IF ( Options_Present ) THEN
          Options_Invalid = .NOT. CRTM_Options_IsValid( Options(m) )
          IF ( Options_Invalid ) THEN
            Error_Status = FAILURE
            WRITE( Message,'("Options data check failed for profile #",i0)' ) m
            CALL Display_Message( ROUTINE_NAME, Message, Error_Status )
            RETURN
          END IF
        END IF
      END IF

      
      ! Process geometry
      ! ...Compute derived geometry
      CALL CRTM_GeometryInfo_SetValue( GeometryInfo, Geometry=Geometry(m) )
      CALL CRTM_GeometryInfo_Compute( GeometryInfo )
      ! ...Retrieve components into local variable
      CALL CRTM_GeometryInfo_GetValue( &
             GeometryInfo, &
             iFOV = iFOV, &
             Source_Zenith_Angle = Source_ZA )


      ! ----------------------------------------------
      ! Add extra layers to current atmosphere profile
      ! if necessary to handle upper atmosphere
      ! ----------------------------------------------
      ! ...Forward model
      Error_Status = CRTM_Atmosphere_AddLayers( Atmosphere(m), &  ! Input
                                                Atm            )  ! Output
      IF ( Error_Status /= SUCCESS ) THEN
        Error_Status = FAILURE
        WRITE( Message,'("Error adding FWD extra layers to profile #",i0)' ) m
        CALL Display_Message( ROUTINE_NAME, TRIM(Message), Error_Status )
        RETURN
      END IF
      ! ...Check the total number of Atm layers
      IF ( Atm%n_Layers > MAX_N_LAYERS ) THEN
        Error_Status = FAILURE
        WRITE( Message,'("Added layers [",i0,"] cause total [",i0,"] to exceed the ",&
               &"maximum allowed [",i0,"] for profile #",i0)' ) &
               Atm%n_Added_Layers, Atm%n_Layers, MAX_N_LAYERS, m
        CALL Display_Message( ROUTINE_NAME, TRIM(Message), Error_Status )
        RETURN
      END IF
      ! ...Similarly extend a copy of the input adjoint atmosphere
      Atm_AD = CRTM_Atmosphere_AddLayerCopy( Atmosphere_AD(m), Atm%n_Added_Layers )
      

      ! -----------------------------------------------------
      ! Allocate all local sensor independent data structures
      ! -----------------------------------------------------
      ! The AtmOptics structure
      AllocStatus(1) = CRTM_Allocate_AtmOptics( Atm%n_Layers        , &  ! Input
                                                MAX_N_LEGENDRE_TERMS, &  ! Input
                                                MAX_N_PHASE_ELEMENTS, &  ! Input
                                                AtmOptics             )  ! Output
      AllocStatus_AD(1) = CRTM_Allocate_AtmOptics( Atm%n_Layers        , &  ! Input
                                                   MAX_N_LEGENDRE_TERMS, &  ! Input
                                                   MAX_N_PHASE_ELEMENTS, &  ! Input
                                                   AtmOptics_AD          )  ! Output
      ! The SfcOptics structure
      AllocStatus(2) = CRTM_Allocate_SfcOptics( MAX_N_ANGLES, &  ! Input
                                                MAX_N_STOKES, &  ! Input
                                                SfcOptics     )  ! Output
      AllocStatus_AD(2) = CRTM_Allocate_SfcOptics( MAX_N_ANGLES, &  ! Input
                                                   MAX_N_STOKES, &  ! Input
                                                   SfcOptics_AD  )  ! Output
      IF ( ANY(AllocStatus    /= SUCCESS) .OR. &
           ANY(AllocStatus_AD /= SUCCESS) ) THEN
        Error_Status=FAILURE
        WRITE( Message,'("Error allocating local sensor independent data structures for profile #",i0)' ) m
        CALL Display_Message( ROUTINE_NAME, TRIM(Message), Error_Status )
        RETURN
      END IF


      ! Average surface skin temperature for multi-surface types
      CALL CRTM_Compute_SurfaceT( Surface(m), SfcOptics )


      ! Allocate the scattering internal variables if necessary
      ! ...Cloud
      IF ( Atm%n_Clouds > 0 ) THEN
        CALL CSvar_Create( CSvar, &
                           MAX_N_LEGENDRE_TERMS, &
                           MAX_N_PHASE_ELEMENTS, &
                           Atm%n_Layers        , &
                           Atm%n_Clouds          )
      END IF
      ! ...Aerosol
      IF ( Atm%n_Aerosols > 0 ) THEN
        CALL ASvar_Create( ASvar, &
                           MAX_N_LEGENDRE_TERMS, &
                           MAX_N_PHASE_ELEMENTS, &
                           Atm%n_Layers        , &
                           Atm%n_Aerosols        )
      END IF


      ! -----------
      ! SENSOR LOOP
      ! -----------
      ! Initialise channel counter for sensor(n)/channel(l) count
      ln = 0
    
      Sensor_Loop: DO n = 1, n_Sensors

        ! Shorter name
        SensorIndex = ChannelInfo(n)%Sensor_Index

        ! Check if antenna correction to be applied for current sensor
        IF ( User_AntCorr .AND. SC(SensorIndex)%AC_Present .AND. iFOV /= 0 ) THEN
          Compute_AntCorr = .TRUE.
        ELSE
          Compute_AntCorr = .FALSE.
        END IF


        ! Allocate the predictor structures
        AllocStatus(1) = CRTM_Allocate_Predictor( SensorIndex , &  ! Input
                                                  Atm%n_Layers, &  ! Input
                                                  Predictor   , &  ! Output
                                                  SaveFWV = 1   )  ! Optional input
        AllocStatus_AD(1) = CRTM_Allocate_Predictor( SensorIndex , &  ! Input
                                                     Atm%n_Layers, &  ! Input
                                                     Predictor_AD  )  ! Output
        IF ( AllocStatus(1) /= SUCCESS .OR. AllocStatus_AD(1) /= SUCCESS ) THEN
          Error_Status=FAILURE
          WRITE( Message,'("Error allocating predictor structures for profile #",i0, &
                 &" and ",a," sensor.")' ) m, SC(SensorIndex)%Sensor_Id
          CALL Display_Message( ROUTINE_NAME, TRIM(Message), Error_Status )
          RETURN
        END IF
        ! Allocate the RTV structure if necessary
        IF( Atm%n_Clouds   > 0 .OR. &
            Atm%n_Aerosols > 0 .OR. &
            SC(SensorIndex)%Sensor_Type == VISIBLE_SENSOR ) THEN
          CALL RTV_Create( RTV, MAX_N_ANGLES, MAX_N_LEGENDRE_TERMS, Atm%n_Layers )
          IF ( .NOT. RTV_Associated(RTV) ) THEN
            Error_Status=FAILURE
            WRITE( Message,'("Error allocating RTV structure for profile #",i0, &
                   &" and ",a," sensor.")' ) m, TRIM(SC(SensorIndex)%Sensor_Id)
            CALL Display_Message( ROUTINE_NAME, Message, Error_Status )
            RETURN
          END IF
        END IF

 
        ! ------------------------------------------
        ! Compute predictors for AtmAbsorption calcs
        ! ------------------------------------------
        CALL CRTM_Compute_Predictors( SensorIndex   , &  ! Input
                                      Atm           , &  ! Input
                                      GeometryInfo  , &  ! Input
                                      AncillaryInput, &  ! Input
                                      Predictor     , &  ! Output
                                      APV             )  ! Internal variable output


        ! ------------
        ! Channel loop
        ! ------------
        Channel_Loop: DO l = 1, ChannelInfo(n)%n_Channels

          ! Shorter name
          ChannelIndex = ChannelInfo(n)%Channel_Index(l)

          ! Increment channel counter
          ln = ln + 1
          
          ! Initialisations
          RTSolution(ln,m)%Radiance       = ZERO
          AtmOptics%Optical_Depth         = ZERO
          AtmOptics%Phase_Coefficient     = ZERO
          AtmOptics%Delta_Truncation      = ZERO
          AtmOptics%Single_Scatter_Albedo = ZERO

          ! ---------------------------------------------------------------
          ! Determine the number of streams (n_Full_Streams) in up+downward
          ! directions. Currently, n_Full_Streams is determined from the
          ! cloud parameters only. It will also use the aerosol parameters 
          ! when aerosol scattering is included.
          ! ---------------------------------------------------------------
          n_Full_Streams = CRTM_Compute_nStreams( Atm             , &  ! Input
                                                  SensorIndex     , &  ! Input
                                                  ChannelIndex    , &  ! Input
                                                  RTSolution(ln,m)  )  ! Output
          ! Transfer the number of streams
          ! to all the scattering structures
          AtmOptics%n_Legendre_Terms    = n_Full_Streams
          AtmOptics_AD%n_Legendre_Terms = n_Full_Streams
          
          
          ! --------------------------
          ! Compute the gas absorption
          ! --------------------------
          CALL CRTM_Compute_AtmAbsorption( SensorIndex   , &  ! Input
                                           ChannelIndex  , &  ! Input
                                           AncillaryInput, &  ! Input
                                           Predictor     , &  ! Input
                                           AtmOptics     , &  ! Output
                                           AAV             )  ! Internal variable output


          ! -------------------------------------------
          ! Compute the molecular scattering properties
          ! -------------------------------------------
           ! Solar radiation
          IF( SC(SensorIndex)%Solar_Irradiance(ChannelIndex) > ZERO .AND. &
              Source_ZA < MAX_SOURCE_ZENITH_ANGLE) THEN
             RTV%Solar_Flag_true = .TRUE.
          END IF
          
          IF( SC(SensorIndex)%Sensor_Type == VISIBLE_SENSOR .and. RTV%Solar_Flag_true ) THEN 
            RTV%Visible_Flag_true = .TRUE.
            ! Rayleigh phase function has 0, 1, 2 components.
            IF( AtmOptics%n_Legendre_Terms < 4 ) THEN
              AtmOptics%n_Legendre_Terms = 4
              RTSolution(ln,m)%Scattering_FLAG = .TRUE.
              RTSolution(ln,m)%n_Full_Streams = AtmOptics%n_Legendre_Terms + 2
            END IF
            RTV%n_Azi = MIN( AtmOptics%n_Legendre_Terms - 1, MAX_N_AZIMUTH_FOURIER )
            ! Get molecular scattering and extinction
            Wavenumber = SC(SensorIndex)%Wavenumber(ChannelIndex)
            Error_Status = CRTM_Compute_MoleculeScatter( &
                             Wavenumber, &
                             Atm       , &
                             AtmOptics   )
            IF ( Error_Status /= SUCCESS ) THEN
              WRITE( Message,'("Error computing MoleculeScatter for ",a,&
                     &", channel ",i0,", profile #",i0)') &
                     TRIM(ChannelInfo(n)%Sensor_ID), &
                     ChannelInfo(n)%Sensor_Channel(l), &
                     m
              CALL Display_Message( ROUTINE_NAME, TRIM(Message), Error_Status )
              RETURN
            END IF
          ELSE
            RTV%Visible_Flag_true = .FALSE.
            RTV%n_Azi = 0
          END IF        
          
          
          ! -----------------------------------------------------------
          ! Compute the cloud particle absorption/scattering properties
          ! -----------------------------------------------------------
          IF( Atm%n_Clouds > 0 ) THEN
            Error_Status = CRTM_Compute_CloudScatter( Atm         , &  ! Input
                                                      SensorIndex , &  ! Input
                                                      ChannelIndex, &  ! Input
                                                      AtmOptics   , &  ! Output
                                                      CSvar         )  ! Internal variable output
            IF (Error_Status /= SUCCESS) THEN
              WRITE( Message,'("Error computing CloudScatter for ",a,&
                     &", channel ",i0,", profile #",i0)' ) &
                     TRIM(ChannelInfo(n)%Sensor_ID), ChannelInfo(n)%Sensor_Channel(l), m
              CALL Display_Message( ROUTINE_NAME, TRIM(Message), Error_Status )
              RETURN
            END IF
          END IF


          ! ----------------------------------------------------
          ! Compute the aerosol absorption/scattering properties
          ! ----------------------------------------------------
          IF ( Atm%n_Aerosols > 0 ) THEN
            Error_Status = CRTM_Compute_AerosolScatter( Atm         , &  ! Input
                                                        SensorIndex , &  ! Input
                                                        ChannelIndex, &  ! Input
                                                        AtmOptics   , &  ! In/Output
                                                        ASvar         )  ! Internal variable output
            IF ( Error_Status /= SUCCESS ) THEN
              WRITE( Message,'("Error computing AerosolScatter for ",a,&
                     &", channel ",i0,", profile #",i0)' ) &
                     TRIM(ChannelInfo(n)%Sensor_ID), ChannelInfo(n)%Sensor_Channel(l), m
              CALL Display_Message( ROUTINE_NAME, TRIM(Message), Error_Status )
              RETURN
            END IF
          END IF


          ! ---------------------------------------------------
          ! Compute the combined atmospheric optical properties
          ! ---------------------------------------------------
          CALL CRTM_Combine_AtmOptics( AtmOptics     , & ! Output
                                       AOV             ) ! Internal variable output


          ! ------------------------------------
          ! Fill the SfcOptics structure for the
          ! optional emissivity input case.
          ! ------------------------------------
          ! Indicate SfcOptics ARE to be computed
          SfcOptics%Compute_Switch = SET
          ! Change SfcOptics emissivity/reflectivity
          ! contents/computation status
          IF ( User_Emissivity ) THEN
            SfcOptics%Compute_Switch  = NOT_SET
            SfcOptics%Emissivity(1,1)       = Options(m)%Emissivity(ln)
            SfcOptics%Reflectivity(1,1,1,1) = ONE - Options(m)%Emissivity(ln)
            IF ( User_Direct_Reflectivity ) THEN
              SfcOptics%Direct_Reflectivity(1,1) = Options(m)%Direct_Reflectivity(ln)
            ELSE
              SfcOptics%Direct_Reflectivity(1,1) = SfcOptics%Reflectivity(1,1,1,1)
            END IF

          END IF

          
        ! ------------
        ! Fourier component loop for azimuth angles,
        ! mth_Azi = 0 is for an azimuth-averaged value,
        ! for example IR and MW thermal radiation
        ! ------------

          RTSolution(ln,m)%Radiance = ZERO
          AtmOptics_AD%Optical_Depth         = ZERO
          AtmOptics_AD%Single_Scatter_Albedo = ZERO
          IF ( AtmOptics%n_Legendre_Terms > 0 ) THEN
            AtmOptics_AD%Phase_Coefficient = ZERO
            AtmOptics_AD%Delta_Truncation  = ZERO
          END IF

        Azi_Fourier_Loop: DO mth_Azi = 0, RTV%n_Azi
          RTV%mth_Azi = mth_Azi
          SfcOptics%mth_Azi = mth_Azi
                    
          ! ------------------------------------
          ! Solve the radiative transfer problem
          ! ------------------------------------
          Error_Status = CRTM_Compute_RTSolution( Atm             , &  ! Input
                                                  Surface(m)      , &  ! Input
                                                  AtmOptics       , &  ! Input
                                                  SfcOptics       , &  ! Input
                                                  GeometryInfo    , &  ! Input
                                                  SensorIndex     , &  ! Input
                                                  ChannelIndex    , &  ! Input
                                                  RTSolution(ln,m), &  ! Output
                                                  RTV               )  ! Internal variable output
          IF ( Error_Status /= SUCCESS ) THEN
            WRITE( Message,'( "Error computing RTSolution for ", a, &
                   &", channel ", i0,", profile #",i0)' ) &
                   TRIM(ChannelInfo(n)%Sensor_ID), ChannelInfo(n)%Sensor_Channel(l), m
            CALL Display_Message( ROUTINE_NAME, TRIM(Message), Error_Status )
            RETURN
          END IF


          ! --------------------------------------
          ! Compute Antenna correction if required
          ! --------------------------------------
          IF ( Compute_AntCorr ) THEN
            CALL CRTM_Compute_AntCorr( GeometryInfo    , &  ! Input
                                       SensorIndex     , &  ! Input
                                       ChannelIndex    , &  ! Input
                                       RTSolution(ln,m)  )  ! Output
            CALL CRTM_Compute_AntCorr_AD( GeometryInfo       , &  ! Input
                                          SensorIndex        , &  ! Input
                                          ChannelIndex       , &  ! Input
                                          RTSolution_AD(ln,m)  )  ! Output
          END IF


          ! -------------------------------------
          ! The adjoint of the radiative transfer
          ! -------------------------------------
          Error_Status = CRTM_Compute_RTSolution_AD( Atm                , &  ! FWD Input
                                                     Surface(m)         , &  ! FWD Input
                                                     AtmOptics          , &  ! FWD Input
                                                     SfcOptics          , &  ! FWD Input
                                                     RTSolution(ln,m)   , &  ! FWD Input
                                                     RTSolution_AD(ln,m), &  ! AD  Input
                                                     GeometryInfo       , &  ! Input
                                                     SensorIndex        , &  ! Input
                                                     ChannelIndex       , &  ! Input
                                                     Atm_AD             , &  ! AD Output
                                                     Surface_AD(m)      , &  ! AD Output
                                                     AtmOptics_AD       , &  ! AD Output
                                                     SfcOptics_AD       , &  ! AD Output
                                                     RTV                  )  ! Internal variable input
          IF ( Error_Status /= SUCCESS ) THEN
            WRITE( Message,'( "Error computing RTSolution_AD for ", a, &
                   &", channel ", i0,", profile #",i0)' ) &
                   TRIM(ChannelInfo(n)%Sensor_ID), ChannelInfo(n)%Sensor_Channel(l), m
            CALL Display_Message( ROUTINE_NAME, TRIM(Message), Error_Status )
            RETURN
          END IF
 

        END DO Azi_Fourier_Loop
        
          ! ------------------------------------------------------------------
          ! Compute the adjoint of the combined atmospheric optical properties
          ! ------------------------------------------------------------------
          CALL CRTM_Combine_AtmOptics_AD( AtmOptics        , &  ! FWD Input
                                          AtmOptics_AD     , &  ! AD  Input
                                          AOV                )  ! Internal variable input


          ! ------------------------------------------------------------
          ! Compute the adjoint aerosol absorption/scattering properties
          ! ------------------------------------------------------------
          IF ( Atm%n_Aerosols > 0 ) THEN
            Error_Status = CRTM_Compute_AerosolScatter_AD( Atm         , &  ! FWD Input
                                                           AtmOptics   , &  ! FWD Input
                                                           AtmOptics_AD, &  ! AD  Input
                                                           SensorIndex , &  ! Input
                                                           ChannelIndex, &  ! Input
                                                           Atm_AD      , &  ! AD  Output
                                                           ASvar         )  ! Internal variable input
            IF ( Error_Status /= SUCCESS ) THEN
              WRITE( Message,'("Error computing AerosolScatter_AD for ",a,&
                     &", channel ",i0,", profile #",i0)' ) &
                     TRIM(ChannelInfo(n)%Sensor_ID), ChannelInfo(n)%Sensor_Channel(l), m
              CALL Display_Message( ROUTINE_NAME, TRIM(Message), Error_Status )
              RETURN
            END IF
          END IF


          ! ----------------------------------------------------------
          ! Compute the adjoint cloud absorption/scattering properties
          ! ----------------------------------------------------------
          IF ( Atm%n_Clouds > 0 ) THEN
            Error_Status = CRTM_Compute_CloudScatter_AD( Atm         , &  ! FWD Input
                                                         AtmOptics   , &  ! FWD Input
                                                         AtmOptics_AD, &  ! AD  Input
                                                         SensorIndex , &  ! Input
                                                         ChannelIndex, &  ! Input
                                                         Atm_AD      , &  ! AD  Output
                                                         CSvar         )  ! Internal variable input
            IF ( Error_Status /= SUCCESS ) THEN
              WRITE( Message,'("Error computing CloudScatter_AD for ",a,&
                     &", channel ",i0,", profile #",i0)' ) &
                     TRIM(ChannelInfo(n)%Sensor_ID), ChannelInfo(n)%Sensor_Channel(l), m
              CALL Display_Message( ROUTINE_NAME, TRIM(Message), Error_Status )
              RETURN
            END IF
          END IF
          
          ! ---------------------------------------------------
          ! Compute the adjoint molecular scattering properties
          ! ---------------------------------------------------
          IF( RTV%Visible_Flag_true ) THEN
            Wavenumber = SC(SensorIndex)%Wavenumber(ChannelIndex)
            Error_Status = CRTM_Compute_MoleculeScatter_AD( &
                             Wavenumber  , &
                             AtmOptics_AD, &
                             Atm_AD        )
            IF ( Error_Status /= SUCCESS ) THEN
              WRITE( Message,'("Error computing MoleculeScatter_AD for ",a,&
                     &", channel ",i0,", profile #",i0)' ) &
                     TRIM(ChannelInfo(n)%Sensor_ID), &
                     ChannelInfo(n)%Sensor_Channel(l), &
                     m
              CALL Display_Message( ROUTINE_NAME, TRIM(Message), Error_Status )
              RETURN
            END IF
          END IF         


          ! --------------------------------------
          ! Compute the adjoint gaseous absorption
          ! --------------------------------------
          CALL CRTM_Compute_AtmAbsorption_AD( SensorIndex     , &  ! Input
                                              ChannelIndex    , &  ! Input
                                              Predictor       , &  ! FWD Input
                                              AtmOptics_AD    , &  ! AD  Input
                                              Predictor_AD    , &  ! AD  Output
                                              AAV               )  ! Internal variable input
        END DO Channel_Loop


        ! -------------------------------------
        ! Adjoint of the predictor calculations
        ! -------------------------------------
        CALL CRTM_Compute_Predictors_AD( SensorIndex   , &  ! Input
                                         Atm           , &  ! FWD Input
                                         Predictor     , &  ! FWD Input
                                         Predictor_AD  , &  ! AD  Input
                                         GeometryInfo  , &  ! Input
                                         AncillaryInput, &  ! Input
                                         Atm_AD        , &  ! AD  Output
                                         APV             )  ! Internal variable input


        ! Deallocate local sensor dependent data structures
        ! ...RTV structure
        IF ( RTV_Associated(RTV) ) CALL RTV_Destroy(RTV)
        ! ...Predictor structures
        AllocStatus(1)    = CRTM_Destroy_Predictor( SensorIndex, Predictor ) 
        AllocStatus_AD(1) = CRTM_Destroy_Predictor( SensorIndex, Predictor_AD )
        IF ( AllocStatus(1) /= SUCCESS .OR. AllocStatus_AD(1) /= SUCCESS ) THEN
          WRITE( Message,'("Error deallocating predictor structures for profile #",i0, &
                 &" and ",a," sensor.")' ) m, SC(SensorIndex)%Sensor_Id
          CALL Display_Message( ROUTINE_NAME, TRIM(Message), Error_Status )
          RETURN
        END IF

      END DO Sensor_Loop

                                        
      ! ---------------------------
      ! Postprocess some input data
      ! ---------------------------
      ! Adjoint of average surface skin temperature for multi-surface types
      CALL CRTM_Compute_SurfaceT_AD( Surface(m), SfcOptics_AD, Surface_AD(m) )


      ! ----------------------------------------
      ! Adjoint of the atmosphere layer addition
      ! ----------------------------------------
      Error_Status = CRTM_Atmosphere_AddLayers_AD( Atmosphere(m)   , &  ! Input
                                                   Atm_AD          , &  ! Input
                                                   Atmosphere_AD(m)  )  ! Output
      IF ( Error_Status /= SUCCESS ) THEN
        Error_Status = FAILURE
        WRITE( Message,'("Error adding AD extra layers to profile #",i0)' ) m
        CALL Display_Message( ROUTINE_NAME, TRIM(Message), Error_Status )
        RETURN
      END IF
      

      ! --------------------------------------------------- 
      ! Deallocate local sensor independent data structures   
      ! ---------------------------------------------------
      AllocStatus_AD(2)=CRTM_Destroy_SfcOptics( SfcOptics_AD )
      AllocStatus(2)   =CRTM_Destroy_SfcOptics( SfcOptics )
      AllocStatus_AD(1)=CRTM_Destroy_AtmOptics( AtmOptics_AD )
      AllocStatus(1)   =CRTM_Destroy_AtmOptics( AtmOptics )
      IF ( ANY(AllocStatus /= SUCCESS ) .OR. ANY(AllocStatus_AD /= SUCCESS ) ) THEN                                              
        Error_Status = FAILURE                                                              
        WRITE( Message,'("Error deallocating local sensor independent data structures for profile #",i0)' ) m  
        CALL Display_Message( ROUTINE_NAME, TRIM(Message), Error_Status )
        RETURN                                                                              
      END IF                                                                                

    END DO Profile_Loop


    ! ---------------------------------
    ! Destroy "extra layers" structures
    ! ---------------------------------
    CALL CRTM_Atmosphere_Destroy( Atm_AD )
    CALL CRTM_Atmosphere_Destroy( Atm )

  END FUNCTION CRTM_Adjoint


!--------------------------------------------------------------------------------
!:sdoc+:
!
! NAME:
!       CRTM_Adjoint_Version
!
! PURPOSE:
!       Subroutine to return the module version information.
!
! CALLING SEQUENCE:
!       CALL CRTM_Adjoint_Version( Id )
!
! OUTPUTS:
!       Id:            Character string containing the version Id information
!                      for the module.
!                      UNITS:      N/A
!                      TYPE:       CHARACTER(*)
!                      DIMENSION:  Scalar
!                      ATTRIBUTES: INTENT(OUT)
!
!:sdoc-:
!--------------------------------------------------------------------------------

  SUBROUTINE CRTM_Adjoint_Version( Id )
    CHARACTER(*), INTENT(OUT) :: Id
    Id = MODULE_VERSION_ID
  END SUBROUTINE CRTM_Adjoint_Version

END MODULE CRTM_Adjoint_Module