Skip to content
Snippets Groups Projects
aircraft_balloon_evol.f90 46.19 KiB
!MNH_LIC Copyright 2000-2023 CNRS, Meteo-France and Universite Paul Sabatier
!MNH_LIC This is part of the Meso-NH software governed by the CeCILL-C licence
!MNH_LIC version 1. See LICENSE, CeCILL-C_V1-en.txt and CeCILL-C_V1-fr.txt
!MNH_LIC for details. version 1.
!-----------------------------------------------------------------
! Author: Valery Masson (Meteo-France *)
!     Original 15/05/2000
! Modifications:
!  G. Jaubert  19/04/2001: add CVBALL type
!  P. Lacarrere   03/2008: add 3D fluxes
!  M. Leriche  12/12/2008: move ZTDIST out from if.not.(tpflyer%fly)
!  V. Masson   15/12/2008: correct do while aircraft move
!  O. Caumont     03/2013: add radar reflectivities
!  C. Lac         04/2014: allow RARE calculation only if CCLOUD=ICE3
!  O. Caumont     05/2014: modify RARE for hydrometeors containing ice + add bright band calculation for RARE
!  C. Lac 02/2015: correction to prevent aircraft crash
!  O. Nuissier/F. Duffourg 07/2015: add microphysics diagnostic for aircraft, ballon and profiler
!  G. Delautier   10/2016: LIMA
!  P. Wautelet 28/03/2018: replace TEMPORAL_DIST by DATETIME_DISTANCE
!  P. Wautelet 05/2016-04/2018: new data structures and calls for I/O
!  P. Wautelet 13/09/2019: budget: simplify and modernize date/time management
!  P. Wautelet 01/10/2020: bugfix: initialize GSTORE
!  P. Wautelet 14/01/2021: bugfixes: -ZXCOEF and ZYCOEF were not computed if CVBALL
!                                    -PCIT was used if CCLOUD/=ICEx (not allocated)
!                                    -PSEA was always used even if not allocated (CSURF/=EXTE)
!                                    -do not use PMAP if cartesian domain
!  P. Wautelet    06/2022: reorganize flyers
!  P. Wautelet 01/06/2023: deduplicate code => moved to modd/mode_sensors.f90
!-----------------------------------------------------------------
!      ##########################
MODULE MODE_AIRCRAFT_BALLOON_EVOL
!      ##########################

USE MODD_PRECISION, ONLY: MNHREAL

USE MODE_MSG

IMPLICIT NONE

PRIVATE

PUBLIC :: AIRCRAFT_BALLOON_EVOL

PUBLIC :: AIRCRAFT_COMPUTE_POSITION

PUBLIC :: FLYER_GET_RANK_MODEL_ISCRASHED

REAL, PARAMETER :: XTIMETHRESH = 1.E-8_MNHREAL

CONTAINS
!     ########################################################
      SUBROUTINE AIRCRAFT_BALLOON_EVOL(PTSTEP,               &
                       PZ, PMAP, PLONOR, PLATOR,             &
                       PU, PV, PW, PP, PTH, PR, PSV, PTKE,   &
                       PTS, PRHODREF, PCIT, TPFLYER,         &
                       KRANK_CUR, KRANK_NXT, PSEA            )
!     ########################################################
!
!
!!****  *AIRCRAFT_BALLOON_EVOL* - (advects and) stores
!!                                balloons/aircrafts in the model
!!
!!    PURPOSE
!!    -------
!
!
!!**  METHOD
!!    ------
!!
!! 1) All the balloons are tested. If the current balloon is
!!     a) in the current model
!!     b) not crashed
!!   the following computations are done.
!!
!! 2) The balloon position is computed.
!!       Interpolations at balloon positions are performed according to mass
!! points (because density is computed here for iso-density balloons).
!! Therefore, all model variables are used at mass points. Shuman averaging
!! are performed on X, Y, Z, U, V, W.
!!
!! 3) Storage of balloon data
!!       If storage is asked for this time-step, the data are recorded in the
!! balloon time-series.
!!
!! 4) Balloon advection
!!       If the balloon is launched, it is advected according its type
!!    a) iso-density balloons are advected following horizontal wind.
!!          the slope of the iso-density surfaces is neglected.
!!    b) radio-sounding balloons are advected according to all wind velocities.
!!          the vertical ascent speed is added to the vertical wind speed.
!!    c) Constant Volume balloons are advected according to all wind velocities.
!!          the vertical ascent speed is computed using the balloon equation
!!
!!
!!    EXTERNAL
!!    --------
!!
!!    IMPLICIT ARGUMENTS
!!    ------------------
!!
!!    REFERENCE
!!    ---------
!!
!! --------------------------------------------------------------------------
!
!*      0. DECLARATIONS
!          ------------
!
USE MODD_AIRCRAFT_BALLOON
USE MODD_CST,              ONLY: XCPD, XLVTT
USE MODD_IO,               ONLY: ISP
USE MODD_TIME_n,           ONLY: TDTCUR
USE MODD_TURB_FLUX_AIRCRAFT_BALLOON, ONLY: XRCW_FLUX, XSVW_FLUX, XTHW_FLUX
!
USE MODE_DATETIME
USE MODE_NEST_ll,          ONLY: GET_MODEL_NUMBER_ll
!
IMPLICIT NONE
!
!
!*      0.1  declarations of arguments
!
!
REAL,                     INTENT(IN)     :: PTSTEP ! time step
REAL, DIMENSION(:,:,:),   INTENT(IN)     :: PZ     ! z array
REAL, DIMENSION(:,:),     INTENT(IN)     :: PMAP   ! map factor
REAL,                     INTENT(IN)     :: PLONOR ! origine longitude
REAL,                     INTENT(IN)     :: PLATOR ! origine latitude
REAL, DIMENSION(:,:,:),   INTENT(IN)     :: PU     ! horizontal wind X component
REAL, DIMENSION(:,:,:),   INTENT(IN)     :: PV     ! horizontal wind Y component
REAL, DIMENSION(:,:,:),   INTENT(IN)     :: PW     ! vertical wind
REAL, DIMENSION(:,:,:),   INTENT(IN)     :: PP     ! pressure
REAL, DIMENSION(:,:,:),   INTENT(IN)     :: PTH    ! potential temperature
REAL, DIMENSION(:,:,:,:), INTENT(IN)     :: PR     ! water mixing ratios
REAL, DIMENSION(:,:,:,:), INTENT(IN)     :: PSV    ! Scalar variables
REAL, DIMENSION(:,:,:),   INTENT(IN)     :: PTKE   ! turbulent kinetic energy
REAL, DIMENSION(:,:),     INTENT(IN)     :: PTS    ! surface temperature
REAL, DIMENSION(:,:,:),   INTENT(IN)     :: PRHODREF ! dry air density of the reference state
REAL, DIMENSION(:,:,:),   INTENT(IN)     :: PCIT     ! pristine ice concentration
!
CLASS(TFLYERDATA),        INTENT(INOUT)  :: TPFLYER ! balloon/aircraft
INTEGER,                  INTENT(IN)     :: KRANK_CUR
INTEGER,                  INTENT(OUT)    :: KRANK_NXT
REAL, DIMENSION(:,:), OPTIONAL, INTENT(IN) :: PSEA
!
!-------------------------------------------------------------------------------
!
!       0.2  declaration of local variables
!
!
INTEGER :: IMI        ! model index
INTEGER :: IKB        ! vertical domain sizes
INTEGER :: IKE
INTEGER :: IKU
!
REAL, DIMENSION(2,2,SIZE(PZ,3))     :: ZZM    ! mass point coordinates
REAL, DIMENSION(2,2,SIZE(PZ,3))     :: ZZU    ! U points z coordinates
REAL, DIMENSION(2,2,SIZE(PZ,3))     :: ZZV    ! V points z coordinates
REAL, DIMENSION(2,2,SIZE(PZ,3))     :: ZWM    ! mass point wind
!
REAL, DIMENSION(2,2,SIZE(PTH,3))    :: ZEXN   ! Exner function
REAL, DIMENSION(2,2,SIZE(PTH,3))    :: ZTH_EXN ! potential temperature multiplied by Exner function
REAL, DIMENSION(2,2,SIZE(PTH,3))    :: ZRHO   ! air density
REAL                                :: ZFLYER_EXN ! balloon/aircraft Exner func.
REAL, DIMENSION(2,2,SIZE(PTH,3))    :: ZTHW_FLUX  !
REAL, DIMENSION(2,2,SIZE(PTH,3))    :: ZRCW_FLUX  !
REAL, DIMENSION(2,2,SIZE(PSV,3),SIZE(PSV,4)) :: ZSVW_FLUX
!
LOGICAL :: GLAUNCH  ! launch/takeoff is effective at this time-step (if true)
LOGICAL :: GOWNER_CUR ! The process is the current owner of the flyer
!
INTEGER :: II_M     ! mass balloon position (x index)
INTEGER :: IJ_M     ! mass balloon position (y index)
INTEGER :: II_U     ! U flux point balloon position (x index)
INTEGER :: IJ_V     ! V flux point balloon position (y index)
!
INTEGER :: ISTORE          ! time index for storage
!
REAL    :: ZTSTEP
TYPE(DATE_TIME) :: TZNEXT ! Time for next position
!----------------------------------------------------------------------------
IKU = SIZE(PZ,3)

CALL GET_MODEL_NUMBER_ll(IMI)

! Set initial value for KRANK_NXT
! It needs to be 0 on all processes except the one where it is when this subroutine is called
! If the flyer flies to an other process, KRANK_NXT will be set accordingly by the current owner
IF ( TPFLYER%NRANK_CUR == ISP ) THEN
  GOWNER_CUR = .TRUE. ! This variable is set and used because NRANK_CUR could change in this subroutine
  KRANK_NXT = ISP
ELSE
  GOWNER_CUR = .FALSE.
  KRANK_NXT = 0
END IF

SELECT TYPE ( TPFLYER )
  CLASS IS ( TAIRCRAFTDATA)
    ! Take-off?
    TAKEOFF: IF ( .NOT. TPFLYER%LTOOKOFF ) THEN
      ! Do the take-off positioning only once
      ! (on model 1 for 'MOB', if aircraft is on an other model, data will be available on the right one anyway)
      IF (      ( TPFLYER%CMODEL == 'MOB' .AND. IMI == 1              ) &
           .OR. ( TPFLYER%CMODEL == 'FIX' .AND. IMI == TPFLYER%NMODEL ) ) THEN
        ! Is the aircraft in flight ?
        IF ( TDTCUR >= TPFLYER%TLAUNCH .AND. TDTCUR <= TPFLYER%TLAND ) THEN
          TPFLYER%LFLY     = .TRUE.
          TPFLYER%LTOOKOFF = .TRUE.
          WRITE( CMNHMSG(1), "( 'aircraft ', A, ' takeoff on ', I2, '/', I2, '/', I4, ' at ', F18.12, 's' )" ) &
                 TRIM( TPFLYER%CNAME ), TDTCUR%NDAY, TDTCUR%NMONTH, TDTCUR%NYEAR, TDTCUR%XTIME
          CALL PRINT_MSG( NVERB_INFO, 'GEN', 'AIRCRAFT_BALLOON_EVOL', OLOCAL = .TRUE. )
        ELSE
          TPFLYER%LFLY = .FALSE.
        END IF
      END IF
    END IF TAKEOFF

    !Do we have to store aircraft data?
    IF ( IMI == TPFLYER%NMODEL ) THEN
      TPFLYER%LSTORE = TPFLYER%TFLYER_TIME%STORESTEP_CHECK_AND_SET( ISTORE )
      IF ( TPFLYER%LSTORE ) TPFLYER%NSTORE_CUR = ISTORE
    END IF


    ! For aircrafts, data has only to be computed at store moments
    IF ( IMI == TPFLYER%NMODEL .AND. TPFLYER%LFLY .AND. TPFLYER%LSTORE ) THEN
      ! Check if it is the right moment to store data
      IF ( ABS( TDTCUR - TPFLYER%TFLYER_TIME%TPDATES(ISTORE) ) < 1e-10 ) THEN
        ISOWNERAIR: IF ( TPFLYER%NRANK_CUR == ISP ) THEN
          CALL FLYER_INTERP_TO_MASSPOINTS()

          ZEXN(:,:,:) = FLYER_COMPUTE_EXNER( )
          ZRHO(:,:,:) = FLYER_COMPUTE_RHO( )

          ZTHW_FLUX(:,:,:) = ZRHO(:,:,:)*XCPD *XTHW_FLUX(II_M:II_M+1,IJ_M:IJ_M+1,:)
          ZRCW_FLUX(:,:,:) = ZRHO(:,:,:)*XLVTT*XRCW_FLUX(II_M:II_M+1,IJ_M:IJ_M+1,:)
          ZSVW_FLUX(:,:,:,:) = XSVW_FLUX(II_M:II_M+1,IJ_M:IJ_M+1,:,:)

          ! Compute coefficents for horizontal interpolations
          CALL FLYER_COMPUTE_INTERP_COEFF_HOR_STAGE1( )
          ! Compute coefficents for vertical interpolations
          CALL FLYER_COMPUTE_INTERP_COEFF_VER( )
          ! Compute coefficents for horizontal interpolations
          CALL FLYER_COMPUTE_INTERP_COEFF_HOR_STAGE2( )

          CALL FLYER_RECORD_DATA( )
        END IF ISOWNERAIR

        ! Store has been done
        TPFLYER%LSTORE = .FALSE.
      END IF
    END IF

    ! Compute next position if the previous store has just been done (right moment on right model)
    IF ( IMI == TPFLYER%NMODEL .AND. ISTORE > 0 ) THEN
      ! This condition may only be tested if ISTORE > 0
      IF (ABS( TDTCUR - TPFLYER%TFLYER_TIME%TPDATES(ISTORE) ) < 1e-10 ) THEN
        ! Next store moment
        TZNEXT = TDTCUR + TPFLYER%TFLYER_TIME%XTSTEP

        ! Is the aircraft in flight ?
        IF ( TZNEXT >= TPFLYER%TLAUNCH .AND. TZNEXT <= TPFLYER%TLAND ) THEN
          TPFLYER%LFLY = .TRUE.
          ! Force LTOOKOFF to prevent to do it again (at a next timestep)
          IF ( .NOT. TPFLYER%LTOOKOFF ) THEN
            TPFLYER%LTOOKOFF = .TRUE.
            WRITE( CMNHMSG(1), "( 'aircraft ', A, ' will take off at next store on ', &
                   I2, '/', I2, '/', I4, ' at ', F18.12, 's' )" )                     &
                   TRIM( TPFLYER%CNAME ), TZNEXT%NDAY, TZNEXT%NMONTH, TZNEXT%NYEAR, TZNEXT%XTIME
            CALL PRINT_MSG( NVERB_INFO, 'GEN', 'AIRCRAFT_BALLOON_EVOL', OLOCAL = .TRUE. )
          END IF

          ! Compute next position
          CALL AIRCRAFT_COMPUTE_POSITION( TZNEXT, TPFLYER )

          ! Get rank of the process where the aircraft is and the model number
          CALL FLYER_GET_RANK_MODEL_ISCRASHED( TPFLYER )

          IF ( TPFLYER%LCRASH .AND. TPFLYER%NCRASH == NCRASH_OUT_HORIZ ) THEN
            WRITE( CMNHMSG(1), "( 'aircraft ', A, ' crashed on ', I2, '/', I2, '/', I4, ' at ', F18.12, &
                   's (out of the horizontal boundaries)' )" )                                          &
                   TRIM( TPFLYER%CNAME ), TZNEXT%NDAY, TZNEXT%NMONTH, TZNEXT%NYEAR, TZNEXT%XTIME
            CALL PRINT_MSG( NVERB_WARNING, 'GEN', 'AIRCRAFT_BALLOON_EVOL', OLOCAL = .TRUE. )
          END IF
        ELSE
          IF ( TPFLYER%LFLY ) THEN
            ! Aircraft was in flight and will have landed at next store
            WRITE( CMNHMSG(1), "( 'aircraft ', A, ' will have landed at next store on ', &
                   I2, '/', I2, '/', I4, ' at ', F18.12, 's' )" )                        &
                   TRIM( TPFLYER%CNAME ), TZNEXT%NDAY, TZNEXT%NMONTH, TZNEXT%NYEAR, TZNEXT%XTIME
            CALL PRINT_MSG( NVERB_INFO, 'GEN', 'AIRCRAFT_BALLOON_EVOL', OLOCAL = .TRUE. )
          END IF
          TPFLYER%LFLY = .FALSE.
        END IF
      END IF
    END IF

    IF ( GOWNER_CUR ) KRANK_NXT = TPFLYER%NRANK_CUR

  CLASS IS ( TBALLOONDATA)
    GLAUNCH = .FALSE. !Set to true only at the launch instant (set to false in flight after launch)

    ! Launch?
    LAUNCH: IF ( .NOT. TPFLYER%LFLY .AND. .NOT. TPFLYER%LCRASH .AND. TPFLYER%NMODEL == IMI ) THEN
      ! Check if it is launchtime
      LAUNCHTIME: IF ( ( TDTCUR - TPFLYER%TLAUNCH ) >= -XTIMETHRESH ) THEN
        TPFLYER%LFLY = .TRUE.
        GLAUNCH = .TRUE.

        TPFLYER%XX_CUR = TPFLYER%XXLAUNCH
        TPFLYER%XY_CUR = TPFLYER%XYLAUNCH
        TPFLYER%TPOS_CUR = TDTCUR
        WRITE( CMNHMSG(1), "( 'balloon ', A, ' launched on ', I2, '/', I2, '/', I4, ' at ', F18.12, 's' )" ) &
               TRIM( TPFLYER%CNAME ), TDTCUR%NDAY, TDTCUR%NMONTH, TDTCUR%NYEAR, TDTCUR%XTIME
        CALL PRINT_MSG( NVERB_INFO, 'GEN', 'AIRCRAFT_BALLOON_EVOL', OLOCAL = .TRUE. )
      END IF LAUNCHTIME
    END IF LAUNCH

    ! Check if it is time to store data. This has also to be checked if the balloon
    ! is not yet launched or is crashed (data is also written in these cases, but with default values)
    IF ( TPFLYER%NMODEL == IMI .AND. &
          ( .NOT. TPFLYER%LFLY .OR. TPFLYER%LCRASH .OR. ABS( TPFLYER%TPOS_CUR - TDTCUR ) < XTIMETHRESH ) ) THEN
      !Do we have to store balloon data?
      TPFLYER%LSTORE = TPFLYER%TFLYER_TIME%STORESTEP_CHECK_AND_SET( ISTORE )
      IF ( TPFLYER%LSTORE ) TPFLYER%NSTORE_CUR = ISTORE
    END IF

    ! In flight
    ! The condition "ABS( TPFLYER%TPOS_CUR - TDTCUR ) < XTIMETHRESH" is necessary if the balloon changes of model
    INFLIGHTONMODEL: IF ( TPFLYER%LFLY .AND. .NOT. TPFLYER%LCRASH .AND. TPFLYER%NMODEL == IMI &
                          .AND. ABS( TPFLYER%TPOS_CUR - TDTCUR ) < XTIMETHRESH ) THEN
      ISOWNERBAL: IF ( TPFLYER%NRANK_CUR == ISP ) THEN
        CALL FLYER_INTERP_TO_MASSPOINTS()

        ZEXN(:,:,:) = FLYER_COMPUTE_EXNER( )
        ZRHO(:,:,:) = FLYER_COMPUTE_RHO( )

        ZTHW_FLUX(:,:,:) = ZRHO(:,:,:)*XCPD *XTHW_FLUX(II_M:II_M+1,IJ_M:IJ_M+1,:)
        ZRCW_FLUX(:,:,:) = ZRHO(:,:,:)*XLVTT*XRCW_FLUX(II_M:II_M+1,IJ_M:IJ_M+1,:)
        ZSVW_FLUX(:,:,:,:) = XSVW_FLUX(II_M:II_M+1,IJ_M:IJ_M+1,:,:)

        ! Compute coefficents for horizontal interpolations
        CALL FLYER_COMPUTE_INTERP_COEFF_HOR_STAGE1( )

        IF ( GLAUNCH ) CALL BALLOON_COMPUTE_INITIAL_VERTICAL_POSITION( TPFLYER )

        ! Compute coefficents for vertical interpolations
        CALL FLYER_COMPUTE_INTERP_COEFF_VER( )

        CRASH_VERT: IF ( TPFLYER%LCRASH ) THEN
          TPFLYER%LFLY = .FALSE.
        ELSE CRASH_VERT
          !No vertical crash

          ! Compute coefficents for horizontal interpolations
          CALL FLYER_COMPUTE_INTERP_COEFF_HOR_STAGE2( )

          ! Check if it is the right moment to store data
          IF ( TPFLYER%LSTORE ) THEN
            ISTORE = TPFLYER%TFLYER_TIME%N_CUR
            IF ( ABS( TDTCUR - TPFLYER%TFLYER_TIME%TPDATES(ISTORE) ) < 1e-10 ) THEN
              CALL FLYER_RECORD_DATA( )
            END IF
          END IF

          ! Compute next horizontal position (balloon advection)
          CALL BALLOON_ADVECTION_HOR( TPFLYER )

          ! Compute next vertical position (balloon advection)
          CALL BALLOON_ADVECTION_VER( TPFLYER )

          TPFLYER%TPOS_CUR = TDTCUR + ZTSTEP
        END IF CRASH_VERT !end of no vertical crash branch
      END IF ISOWNERBAL
    END IF INFLIGHTONMODEL

    IF ( GOWNER_CUR ) KRANK_NXT = TPFLYER%NRANK_CUR
END SELECT

CONTAINS

!----------------------------------------------------------------------------
!----------------------------------------------------------------------------
SUBROUTINE BALLOON_COMPUTE_INITIAL_VERTICAL_POSITION( TPBALLOON )

USE MODD_CST, ONLY: XCPD, XP00, XRD

IMPLICIT NONE

CLASS(TBALLOONDATA), INTENT(INOUT) :: TPBALLOON

LOGICAL :: GLOW, GHIGH

GLOW  = .FALSE.
GHIGH = .FALSE.

SELECT CASE ( TPBALLOON%CTYPE )
  !
  ! Iso-density balloon
  !
  CASE ( 'ISODEN' )
    IF ( TPBALLOON%XALTLAUNCH /= XNEGUNDEF ) THEN
      CALL TPBALLOON%COMPUTE_VERTICAL_INTERP_COEFF( 'MASS', TPBALLOON%XALTLAUNCH, ZZM, GLOW, GHIGH )
      TPBALLOON%XRHO = TPBALLOON%INTERP_FROM_MASSPOINT( ZRHO )
    ELSE IF ( TPBALLOON%XPRES /= XNEGUNDEF ) THEN
      ZFLYER_EXN = (TPBALLOON%XPRES/XP00)**(XRD/XCPD)
      CALL TPBALLOON%COMPUTE_VERTICAL_INTERP_COEFF( 'MASS', ZFLYER_EXN, ZEXN, GLOW, GHIGH )
      TPBALLOON%XRHO = TPBALLOON%INTERP_FROM_MASSPOINT( ZRHO )
    ELSE
      CMNHMSG(1) = 'Error in balloon initial position (balloon ' // TRIM(TPBALLOON%CNAME) // ' )'
      CMNHMSG(2) = 'neither initial ALTITUDE or PRESsure is given'
      CMNHMSG(3) = 'Check your INI_BALLOON routine'
      CALL PRINT_MSG( NVERB_FATAL, 'GEN', 'AIRCRAFT_BALLOON_EVOL', OLOCAL = .TRUE. )
    END IF
  !
  ! Radiosounding balloon
  !
  CASE ( 'RADIOS' )
    TPBALLOON%XZ_CUR = TPBALLOON%XALTLAUNCH
    TPBALLOON%XZ_CUR = MAX ( TPBALLOON%XZ_CUR , ZZM(1,1,IKB) )
    TPBALLOON%XZ_CUR = MAX ( TPBALLOON%XZ_CUR , ZZM(2,1,IKB) )
    TPBALLOON%XZ_CUR = MAX ( TPBALLOON%XZ_CUR , ZZM(1,2,IKB) )
    TPBALLOON%XZ_CUR = MAX ( TPBALLOON%XZ_CUR , ZZM(2,2,IKB) )
    IF ( TPBALLOON%XZ_CUR > TPBALLOON%XALTLAUNCH ) THEN
      WRITE( CMNHMSG(1), '(A)' ) 'initial vertical position of ' // TRIM( TPBALLOON%CNAME ) // ' was too low'
      WRITE( CMNHMSG(2), '( "forced to ", EN12.3, " (instead of ", EN12.3, ")" )' ) TPBALLOON%XZ_CUR, TPBALLOON%XALTLAUNCH
      CALL PRINT_MSG( NVERB_WARNING, 'GEN', 'BALLOON_COMPUTE_INITIAL_VERTICAL_POSITION', OLOCAL = .TRUE. )
    END IF
  !
  ! Constant Volume Balloon
  !
  CASE ( 'CVBALL' )
    IF ( TPBALLOON%XALTLAUNCH /= XNEGUNDEF ) THEN
      CALL TPBALLOON%COMPUTE_VERTICAL_INTERP_COEFF( 'MASS', TPBALLOON%XALTLAUNCH, ZZM, GLOW, GHIGH )
      IF ( GLOW ) THEN
        TPBALLOON%XZ_CUR = TPBALLOON%XALTLAUNCH
        TPBALLOON%XZ_CUR = MAX ( TPBALLOON%XZ_CUR , ZZM(1,1,IKB) )
        TPBALLOON%XZ_CUR = MAX ( TPBALLOON%XZ_CUR , ZZM(2,1,IKB) )
        TPBALLOON%XZ_CUR = MAX ( TPBALLOON%XZ_CUR , ZZM(1,2,IKB) )
        TPBALLOON%XZ_CUR = MAX ( TPBALLOON%XZ_CUR , ZZM(2,2,IKB) )

        WRITE( CMNHMSG(1), '(A)' ) 'initial vertical position of ' // TRIM( TPBALLOON%CNAME ) // ' was too low'
        WRITE( CMNHMSG(2), '( "forced to ", EN12.3, " (instead of ", EN12.3, ")" )' ) TPBALLOON%XZ_CUR, TPBALLOON%XALTLAUNCH
        CALL PRINT_MSG( NVERB_WARNING, 'GEN', 'BALLOON_COMPUTE_INITIAL_VERTICAL_POSITION', OLOCAL = .TRUE. )

        !Recompute the vertical interpolation coefficients at the corrected vertical position
        CALL TPBALLOON%COMPUTE_VERTICAL_INTERP_COEFF( 'MASS', TPBALLOON%XALTLAUNCH, ZZM, GLOW, GHIGH )
      ELSE
        TPBALLOON%XZ_CUR = TPBALLOON%INTERP_FROM_MASSPOINT( ZZM )
      END IF
      TPBALLOON%XRHO     = TPBALLOON%INTERP_FROM_MASSPOINT( ZRHO )
    ELSE IF ( TPBALLOON%XPRES /= XNEGUNDEF ) THEN
      ZFLYER_EXN = (TPBALLOON%XPRES/XP00)**(XRD/XCPD)
      CALL TPBALLOON%COMPUTE_VERTICAL_INTERP_COEFF( 'MASS', ZFLYER_EXN, ZEXN, GLOW, GHIGH )
      IF ( GLOW ) THEN
        TPBALLOON%XZ_CUR = ZZM(1,1,IKB)
        TPBALLOON%XZ_CUR = MAX ( TPBALLOON%XZ_CUR , ZZM(2,1,IKB) )
        TPBALLOON%XZ_CUR = MAX ( TPBALLOON%XZ_CUR , ZZM(1,2,IKB) )
        TPBALLOON%XZ_CUR = MAX ( TPBALLOON%XZ_CUR , ZZM(2,2,IKB) )

        WRITE( CMNHMSG(1), '(A)' ) 'initial vertical position of ' // TRIM( TPBALLOON%CNAME ) // ' was too low'
        WRITE( CMNHMSG(2), '( "forced to ", EN12.3 )' ) TPBALLOON%XZ_CUR
        CALL PRINT_MSG( NVERB_WARNING, 'GEN', 'BALLOON_COMPUTE_INITIAL_VERTICAL_POSITION', OLOCAL = .TRUE. )

        !Recompute the vertical interpolation coefficients at the corrected vertical position
        CALL TPBALLOON%COMPUTE_VERTICAL_INTERP_COEFF( 'MASS', TPBALLOON%XZ_CUR, ZZM, GLOW, GHIGH )
      ELSE
        TPBALLOON%XZ_CUR = TPBALLOON%INTERP_FROM_MASSPOINT( ZZM )
      END IF
      TPBALLOON%XRHO     = TPBALLOON%INTERP_FROM_MASSPOINT( ZRHO )
    ELSE
      TPBALLOON%XRHO = TPBALLOON%XMASS / TPBALLOON%XVOLUME
      CALL TPBALLOON%COMPUTE_VERTICAL_INTERP_COEFF( 'MASS', TPBALLOON%XRHO, ZRHO, GLOW, GHIGH )
      IF ( GLOW ) THEN
        TPBALLOON%XZ_CUR = ZZM(1,1,IKB)
        TPBALLOON%XZ_CUR = MAX ( TPBALLOON%XZ_CUR , ZZM(2,1,IKB) )
        TPBALLOON%XZ_CUR = MAX ( TPBALLOON%XZ_CUR , ZZM(1,2,IKB) )
        TPBALLOON%XZ_CUR = MAX ( TPBALLOON%XZ_CUR , ZZM(2,2,IKB) )

        WRITE( CMNHMSG(1), '(A)' ) 'initial vertical position of ' // TRIM( TPBALLOON%CNAME ) // ' was too low'
        WRITE( CMNHMSG(2), '( "forced to ", EN12.3 )' ) TPBALLOON%XZ_CUR
        CALL PRINT_MSG( NVERB_WARNING, 'GEN', 'BALLOON_COMPUTE_INITIAL_VERTICAL_POSITION', OLOCAL = .TRUE. )

        !Recompute the vertical interpolation coefficients at the corrected vertical position
        CALL TPBALLOON%COMPUTE_VERTICAL_INTERP_COEFF( 'MASS', TPBALLOON%XZ_CUR, ZZM, GLOW, GHIGH )
      ELSE
        TPBALLOON%XZ_CUR = TPBALLOON%INTERP_FROM_MASSPOINT( ZZM )
      END IF
    END IF
END SELECT

END SUBROUTINE BALLOON_COMPUTE_INITIAL_VERTICAL_POSITION
!----------------------------------------------------------------------------
!----------------------------------------------------------------------------
SUBROUTINE BALLOON_ADVECTION_HOR( TPBALLOON )

USE MODD_AIRCRAFT_BALLOON, ONLY: TBALLOONDATA
USE MODD_CONF,             ONLY: LCARTESIAN
USE MODD_NESTING,          ONLY: NDAD, NDTRATIO
USE MODD_TIME,             only: TDTSEG
USE MODD_TIME_n,           ONLY: TDTCUR

USE MODE_DATETIME

IMPLICIT NONE

CLASS(TBALLOONDATA), INTENT(INOUT) :: TPBALLOON

INTEGER :: IMODEL
INTEGER :: IMODEL_OLD
REAL    :: ZX_OLD, ZY_OLD
REAL    :: ZDELTATIME
REAL    :: ZDIVTMP
REAL    :: ZMAP     ! map factor at balloon location
REAL    :: ZU_BAL   ! horizontal wind speed at balloon location (along x)
REAL    :: ZV_BAL   ! horizontal wind speed at balloon location (along y)
TYPE(DATE_TIME) :: TZNEXT ! Time for next position

ZTSTEP = PTSTEP

ZU_BAL = TPBALLOON%INTERP_FROM_UPOINT( PU )
ZV_BAL = TPBALLOON%INTERP_FROM_VPOINT( PV )
if ( .not. lcartesian ) then
  ZMAP = TPBALLOON%INTERP_HOR_FROM_MASSPOINT( PMAP )
else
  ZMAP = 1.
end if
!
ZX_OLD = TPBALLOON%XX_CUR
ZY_OLD = TPBALLOON%XY_CUR

TPBALLOON%XX_CUR = TPBALLOON%XX_CUR + ZU_BAL * ZTSTEP * ZMAP
TPBALLOON%XY_CUR = TPBALLOON%XY_CUR + ZV_BAL * ZTSTEP * ZMAP

! Compute rank and model for next position
! This is done here because we need to check if there is a change of model (for 'MOB' balloons)
! because position has to be adapted to the timestep of a coarser model (if necessary)
IMODEL_OLD = TPBALLOON%NMODEL

! Get rank of the process where the balloon is and the model number
CALL FLYER_GET_RANK_MODEL_ISCRASHED( TPBALLOON )

TZNEXT = TDTCUR + ZTSTEP

IF ( TPBALLOON%LCRASH .AND. TPBALLOON%NCRASH == NCRASH_OUT_HORIZ ) THEN
  WRITE( CMNHMSG(1), "( 'balloon ', A, ' crashed on ', I2, '/', I2, '/', I4, ' at ', F18.12, &
         's (out of the horizontal boundaries)' )" )                                          &
         TRIM( TPBALLOON%CNAME ), TZNEXT%NDAY, TZNEXT%NMONTH, TZNEXT%NYEAR, TZNEXT%XTIME
  CALL PRINT_MSG( NVERB_WARNING, 'GEN', 'AIRCRAFT_BALLOON_EVOL', OLOCAL = .TRUE. )
END IF

IF ( TPBALLOON%NMODEL /= IMODEL_OLD .AND. .NOT. TPBALLOON%LCRASH ) THEN
  ! Balloon has changed of model
  IF ( NDAD(TPBALLOON%NMODEL ) == IMODEL_OLD ) THEN
    ! Nothing special to do when going to child model
    WRITE( CMNHMSG(1), "( 'balloon ', A, ': change of model: ', I2, '->', I2, ' going to child' )" ) &
           TRIM( TPFLYER%CNAME ), IMODEL_OLD, TPBALLOON%NMODEL
    WRITE( CMNHMSG(2), "( 'on ', I2, '/', I2, '/', I4, ' at ', F18.12, 's' )" ) &
           TZNEXT%NDAY, TZNEXT%NMONTH, TZNEXT%NYEAR, TZNEXT%XTIME
    CALL PRINT_MSG( NVERB_INFO, 'GEN', 'AIRCRAFT_BALLOON_EVOL', OLOCAL = .TRUE. )
  ELSE IF ( TPBALLOON%NMODEL == NDAD(IMODEL_OLD) ) THEN
    ! Balloon go to parent model
    ! Recompute position to be compatible with parent timestep
    ! Parent timestep could be bigger (factor NDTRATIO) and therefore next position is not the one computed just before

    ! Determine step compatible with parent model at next parent timestep
    ZDELTATIME = TDTCUR - TDTSEG
    ZDIVTMP = ZDELTATIME / ( PTSTEP * NDTRATIO(IMODEL_OLD) )
    IF ( ABS( ZDIVTMP - NINT( ZDIVTMP ) ) < 1E-6 * PTSTEP * NDTRATIO(IMODEL_OLD) ) THEN
      ! Current time is a multiple of parent timestep => next position is parent timestep
      ZTSTEP = ZTSTEP * NDTRATIO(IMODEL_OLD)
    ELSE
      ! Current time is not a multiple of parent timestep
      ! Next position must be a multiple of parent timestep
      ! NINT( NDTRATIO(IMODEL_OLD) * ( 1 - ( ZDIVTMP - INT( ZDIVTMP ) ) ) ) corresponds to the number
      ! of child timesteps to go to the next parent timestep
      ! We skip one timestep (+NDTRATIO(IMODEL_OLD)) because it has already been computed for the parent model
      ZTSTEP = ZTSTEP * ( NINT( NDTRATIO(IMODEL_OLD) * ( 1 - ( ZDIVTMP - INT( ZDIVTMP ) ) ) ) + NDTRATIO(IMODEL_OLD) )

      ! Detect if we need to skip a store (if time of next position is after time of next store)
      ! This can happen when a ballon goes to its parent model
      IF ( TDTCUR + ZTSTEP > TPBALLOON%TFLYER_TIME%TPDATES(TPBALLOON%TFLYER_TIME%N_CUR) + TPBALLOON%TFLYER_TIME%XTSTEP + 1e-6 ) THEN
        !Force a dummy store (nothing is computed, therefore default/initial values will be stored)
        TPBALLOON%LSTORE = .TRUE.

        TPBALLOON%TFLYER_TIME%N_CUR = TPBALLOON%TFLYER_TIME%N_CUR + 1
        ISTORE = TPBALLOON%TFLYER_TIME%N_CUR

        !Remark: by construction here, ISTORE is always > 1 => no risk with ISTORE-1 value
        TPBALLOON%TFLYER_TIME%TPDATES(ISTORE) = TPBALLOON%TFLYER_TIME%TPDATES(ISTORE-1) + TPBALLOON%TFLYER_TIME%XTSTEP

        WRITE( CMNHMSG(1), "( 'balloon ', A, ': store skipped on ', I2, '/', I2, '/', I4, ' at ', F18.12, 's' )" ) &
               TRIM( TPBALLOON%CNAME ),                                                                            &
               TPBALLOON%TFLYER_TIME%TPDATES(ISTORE)%NDAY,  TPBALLOON%TFLYER_TIME%TPDATES(ISTORE)%NMONTH,          &
               TPBALLOON%TFLYER_TIME%TPDATES(ISTORE)%NYEAR, TPBALLOON%TFLYER_TIME%TPDATES(ISTORE)%XTIME
        CMNHMSG(2) = 'due to change of model (child to its parent)'
        CALL PRINT_MSG( NVERB_INFO, 'GEN', 'AIRCRAFT_BALLOON_EVOL', OLOCAL = .TRUE. )
      END IF
    END IF

    TZNEXT = TDTCUR + ZTSTEP

    WRITE( CMNHMSG(1), "( 'balloon ', A, ': change of model: ', I2, '->', I2, ' going to parent' )" ) &
           TRIM( TPFLYER%CNAME ), IMODEL_OLD, TPBALLOON%NMODEL
    WRITE( CMNHMSG(2), "( 'on ', I2, '/', I2, '/', I4, ' at ', F18.12, 's' )" ) &
           TZNEXT%NDAY, TZNEXT%NMONTH, TZNEXT%NYEAR, TZNEXT%XTIME
    CALL PRINT_MSG( NVERB_INFO, 'GEN', 'AIRCRAFT_BALLOON_EVOL', OLOCAL = .TRUE. )

    ! Compute new horizontal position
    TPBALLOON%XX_CUR = TPBALLOON%XX_CUR + ZU_BAL * ZTSTEP * ZMAP
    TPBALLOON%XY_CUR = TPBALLOON%XY_CUR + ZV_BAL * ZTSTEP * ZMAP

    ! Get rank of the process where the balloon is and the model number
    ! Model number is now imposed
    IMODEL = TPBALLOON%NMODEL
    CALL FLYER_GET_RANK_MODEL_ISCRASHED( TPBALLOON, KMODEL = IMODEL )

    IF ( TPBALLOON%LCRASH .AND. TPBALLOON%NCRASH == NCRASH_OUT_HORIZ ) THEN
      WRITE( CMNHMSG(1), "( 'balloon ', A, ' crashed on ', I2, '/', I2, '/', I4, ' at ', F18.12, &
             's (out of the horizontal boundaries)' )" )                                          &
             TRIM( TPBALLOON%CNAME ), TZNEXT%NDAY, TZNEXT%NMONTH, TZNEXT%NYEAR, TZNEXT%XTIME
      CALL PRINT_MSG( NVERB_WARNING, 'GEN', 'AIRCRAFT_BALLOON_EVOL', OLOCAL = .TRUE. )
    END IF
  ELSE
    ! Special case not-managed (different dads, change of several models in 1 step (going to grand parent/grand children)...)
    ! This situation should be very infrequent => reasonable risk, error on the trajectory should be relatively small in most cases
    CMNHMSG(1) = 'unmanaged change of model for ballon ' // TPBALLOON%CNAME
    CMNHMSG(2) = 'its trajectory might be wrong'
    CALL PRINT_MSG( NVERB_WARNING, 'GEN', 'AIRCRAFT_BALLOON_EVOL', OLOCAL = .TRUE. )
  END IF
END IF

END SUBROUTINE BALLOON_ADVECTION_HOR
!----------------------------------------------------------------------------
!----------------------------------------------------------------------------
SUBROUTINE BALLOON_ADVECTION_VER( TPBALLOON )

USE MODD_AIRCRAFT_BALLOON, ONLY: TBALLOONDATA
USE MODD_CST,              ONLY: XG

IMPLICIT NONE

CLASS(TBALLOONDATA), INTENT(INOUT) :: TPBALLOON

INTEGER :: JK ! loop index
REAL    :: ZRO_BAL  ! air density at balloon location
REAL    :: ZW_BAL   ! vertical   wind speed at balloon location (along z)

IF ( TPBALLOON%CTYPE == 'RADIOS' ) THEN
  ZW_BAL = TPBALLOON%INTERP_FROM_MASSPOINT( ZWM )
  TPBALLOON%XZ_CUR = TPBALLOON%XZ_CUR + ( ZW_BAL + TPBALLOON%XWASCENT ) * ZTSTEP
END IF

IF ( TPBALLOON%CTYPE == 'CVBALL' ) THEN
  ZW_BAL  = TPBALLOON%INTERP_FROM_MASSPOINT( ZWM )
  ZRO_BAL = TPBALLOON%INTERP_FROM_MASSPOINT( ZRHO )
  ! calculation with a time step of 1 second or less
  IF (INT(ZTSTEP) .GT. 1 ) THEN
    DO JK=1,INT(ZTSTEP)
      TPBALLOON%XWASCENT = TPBALLOON%XWASCENT &
        -  ( 1. / (1. + TPBALLOON%XINDDRAG ) ) * 1. * &
           ( XG * ( ( TPBALLOON%XMASS / TPBALLOON%XVOLUME ) - ZRO_BAL ) / ( TPBALLOON%XMASS / TPBALLOON%XVOLUME ) &
              + TPBALLOON%XWASCENT * ABS ( TPBALLOON%XWASCENT ) * &
                TPBALLOON%XDIAMETER * TPBALLOON%XAERODRAG / ( 2. * TPBALLOON%XVOLUME ) &
            )
      TPBALLOON%XZ_CUR = TPBALLOON%XZ_CUR + ( ZW_BAL + TPBALLOON%XWASCENT ) * 1.
    END DO
  END IF
  IF (ZTSTEP .GT. INT(ZTSTEP)) THEN
    TPBALLOON%XWASCENT = TPBALLOON%XWASCENT &
      -  ( 1. / (1. + TPBALLOON%XINDDRAG ) ) * (ZTSTEP-INT(ZTSTEP)) * &
         ( XG * ( ( TPBALLOON%XMASS / TPBALLOON%XVOLUME ) - ZRO_BAL ) / ( TPBALLOON%XMASS / TPBALLOON%XVOLUME ) &
            + TPBALLOON%XWASCENT * ABS ( TPBALLOON%XWASCENT ) * &
              TPBALLOON%XDIAMETER * TPBALLOON%XAERODRAG / ( 2. * TPBALLOON%XVOLUME ) &
          )
    TPBALLOON%XZ_CUR = TPBALLOON%XZ_CUR + ( ZW_BAL + TPBALLOON%XWASCENT ) * (ZTSTEP-INT(ZTSTEP))
  END IF
END IF

END SUBROUTINE BALLOON_ADVECTION_VER
!----------------------------------------------------------------------------
!----------------------------------------------------------------------------
SUBROUTINE FLYER_INTERP_TO_MASSPOINTS()

USE MODD_GRID_n,     ONLY: XXHAT, XXHATM, XYHAT, XYHATM
USE MODD_PARAMETERS, ONLY: JPVEXT

IMPLICIT NONE

INTEGER :: IDU      ! difference between II_U and II_M
INTEGER :: IDV      ! difference between IJ_V and IJ_M

! Indices
IKB =   1   + JPVEXT
IKE = SIZE(PZ,3) - JPVEXT

! Interpolations of model variables to mass points
! ------------------------------------------------

! X position
TPFLYER%NI_U = COUNT( XXHAT (:) <= TPFLYER%XX_CUR )
TPFLYER%NI_M = COUNT( XXHATM(:) <= TPFLYER%XX_CUR )
II_U = TPFLYER%NI_U
II_M = TPFLYER%NI_M

! Y position
TPFLYER%NJ_V = COUNT( XYHAT (:)<=TPFLYER%XY_CUR )
TPFLYER%NJ_M = COUNT( XYHATM(:)<=TPFLYER%XY_CUR )
IJ_V = TPFLYER%NJ_V
IJ_M = TPFLYER%NJ_M

ZZM(:,:,1:IKU-1)=0.5 *PZ(II_M  :II_M+1,IJ_M  :IJ_M+1,1:IKU-1)+0.5 *PZ(II_M  :II_M+1,IJ_M  :IJ_M+1,2:IKU  )
ZZM(:,:,  IKU  )=1.5 *PZ(II_M  :II_M+1,IJ_M  :IJ_M+1,  IKU-1)-0.5 *PZ(II_M  :II_M+1,IJ_M  :IJ_M+1,  IKU-2)

IDU = II_U - II_M
ZZU(:,:,1:IKU-1)=0.25*PZ(IDU+II_M-1:IDU+II_M,  IJ_M  :IJ_M+1,1:IKU-1)+0.25*PZ(IDU+II_M-1:IDU+II_M  ,IJ_M  :IJ_M+1,2:IKU  ) &
                +0.25*PZ(IDU+II_M  :IDU+II_M+1,IJ_M  :IJ_M+1,1:IKU-1)+0.25*PZ(IDU+II_M  :IDU+II_M+1,IJ_M  :IJ_M+1,2:IKU  )
ZZU(:,:,  IKU  )=0.75*PZ(IDU+II_M-1:IDU+II_M  ,IJ_M  :IJ_M+1,  IKU-1)-0.25*PZ(IDU+II_M-1:IDU+II_M  ,IJ_M  :IJ_M+1,  IKU-2) &
                +0.75*PZ(IDU+II_M  :IDU+II_M+1,IJ_M  :IJ_M+1,  IKU-1)-0.25*PZ(IDU+II_M  :IDU+II_M+1,IJ_M  :IJ_M+1,  IKU-2)

IDV = IJ_V - IJ_M
ZZV(:,:,1:IKU-1)=0.25*PZ(II_M  :II_M+1,IDV+IJ_M-1:IDV+IJ_M  ,1:IKU-1)+0.25*PZ(II_M  :II_M+1,IDV+IJ_M-1:IDV+IJ_M  ,2:IKU  ) &
                +0.25*PZ(II_M  :II_M+1,IDV+IJ_M  :IDV+IJ_M+1,1:IKU-1)+0.25*PZ(II_M  :II_M+1,IDV+IJ_M  :IDV+IJ_M+1,2:IKU  )
ZZV(:,:,  IKU  )=0.75*PZ(II_M  :II_M+1,IDV+IJ_M-1:IDV+IJ_M  ,  IKU-1)-0.25*PZ(II_M  :II_M+1,IDV+IJ_M-1:IDV+IJ_M  ,  IKU-2) &
                +0.75*PZ(II_M  :II_M+1,IDV+IJ_M  :IDV+IJ_M+1,  IKU-1)-0.25*PZ(II_M  :II_M+1,IDV+IJ_M  :IDV+IJ_M+1,  IKU-2)

ZWM(:,:,1:IKU-1)=0.5*PW(II_M:II_M+1,IJ_M:IJ_M+1,1:IKU-1)+0.5*PW(II_M:II_M+1,IJ_M:IJ_M+1,2:IKU  )
ZWM(:,:,  IKU  )=1.5*PW(II_M:II_M+1,IJ_M:IJ_M+1,  IKU-1)-0.5*PW(II_M:II_M+1,IJ_M:IJ_M+1,  IKU-2)

END SUBROUTINE FLYER_INTERP_TO_MASSPOINTS
!----------------------------------------------------------------------------
!----------------------------------------------------------------------------
PURE FUNCTION FLYER_COMPUTE_EXNER( ) RESULT( PEXN )

USE MODD_CST, ONLY: XCPD, XP00, XRD

IMPLICIT NONE

REAL, DIMENSION(2,2,SIZE(PTH,3)) :: PEXN

INTEGER :: JK

PEXN(:,:,:) = ( PP(II_M:II_M+1, IJ_M:IJ_M+1, :) / XP00) ** ( XRD / XCPD )
DO JK = IKB-1, 1, -1
  PEXN(:,:,JK) = 1.5 * PEXN(:,:,JK+1) - 0.5 * PEXN(:,:,JK+2)
END DO
DO JK = IKE+1, IKU
  PEXN(:,:,JK) = 1.5 * PEXN(:,:,JK-1) - 0.5 * PEXN(:,:,JK-2)
END DO

END FUNCTION FLYER_COMPUTE_EXNER
!----------------------------------------------------------------------------
!----------------------------------------------------------------------------
PURE FUNCTION FLYER_COMPUTE_RHO( ) RESULT( PRHO )

USE MODD_CST, ONLY: XRD, XRV

USE MODI_WATER_SUM

IMPLICIT NONE

REAL, DIMENSION(2,2,SIZE(PTH,3)) :: PRHO

INTEGER                          :: JK
REAL, DIMENSION(2,2,SIZE(PTH,3)) :: ZTHV ! virtual potential temperature

ZTHV(:,:,:) = PTH(II_M:II_M+1, IJ_M:IJ_M+1, :)
IF ( SIZE( PR, 4 ) > 0 )                                                              &
  ZTHV(:,:,:) = ZTHV(:,:,:) * ( 1. + XRV / XRD * PR(II_M:II_M+1, IJ_M:IJ_M+1, :, 1) ) &
                            / ( 1. + WATER_SUM( PR(II_M:II_M+1, IJ_M:IJ_M+1, :, :)) )
!
PRHO(:,:,:) = PP(II_M:II_M+1, IJ_M:IJ_M+1, :) / ( XRD * ZTHV(:,:,:) * ZEXN(:,:,:) )
DO JK = IKB-1, 1, -1
  PRHO(:,:,JK) = 1.5 * PRHO(:,:,JK+1) - 0.5 * PRHO(:,:,JK+2)
END DO
DO JK = IKE+1, IKU
  PRHO(:,:,JK) = 1.5 * PRHO(:,:,JK-1) - 0.5 * PRHO(:,:,JK-2)
END DO

END FUNCTION FLYER_COMPUTE_RHO
!----------------------------------------------------------------------------
!----------------------------------------------------------------------------
SUBROUTINE FLYER_COMPUTE_INTERP_COEFF_HOR_STAGE1( )
! Compute coefficents for horizontal interpolations (1st stage)

USE MODD_GRID_n, ONLY: XXHAT, XXHATM, XYHAT, XYHATM

IMPLICIT NONE

! Interpolation coefficient for X
TPFLYER%XXMCOEF = ( TPFLYER%XX_CUR - XXHATM(II_M) ) / ( XXHATM(II_M+1) - XXHATM(II_M) )
TPFLYER%XXMCOEF = MAX( 0., MIN( TPFLYER%XXMCOEF, 1. ) )

! Interpolation coefficient for y
TPFLYER%XYMCOEF = ( TPFLYER%XY_CUR - XYHATM(IJ_M) ) / ( XYHATM(IJ_M+1) - XYHATM(IJ_M) )
TPFLYER%XYMCOEF = MAX( 0., MIN( TPFLYER%XYMCOEF, 1. ) )

! Interpolation coefficient for X (for U)
TPFLYER%XXUCOEF = ( TPFLYER%XX_CUR - XXHAT(II_U) ) / ( XXHAT(II_U+1) - XXHAT(II_U) )
TPFLYER%XXUCOEF = MAX( 0., MIN( TPFLYER%XXUCOEF, 1. ) )

! Interpolation coefficient for y (for V)
TPFLYER%XYVCOEF = ( TPFLYER%XY_CUR - XYHAT(IJ_V) ) / ( XYHAT(IJ_V+1) - XYHAT(IJ_V) )
TPFLYER%XYVCOEF = MAX( 0., MIN( TPFLYER%XYVCOEF, 1. ) )

END SUBROUTINE FLYER_COMPUTE_INTERP_COEFF_HOR_STAGE1
!----------------------------------------------------------------------------
!----------------------------------------------------------------------------
SUBROUTINE FLYER_COMPUTE_INTERP_COEFF_VER( )
! Compute coefficent for vertical interpolations

USE MODD_CST,    ONLY: XCPD, XP00, XRD
USE MODD_TIME_n, ONLY: TDTCUR

IMPLICIT NONE

LOGICAL :: GLOW, GHIGH

! Find indices surrounding the vertical box where the flyer is
SELECT TYPE ( TPFLYER )
  CLASS IS ( TAIRCRAFTDATA )
    IF ( TPFLYER%LALTDEF ) THEN
      ZFLYER_EXN = (TPFLYER%XP_CUR/XP00)**(XRD/XCPD)
      CALL TPFLYER%COMPUTE_VERTICAL_INTERP_COEFF( 'MASS', ZFLYER_EXN,     ZEXN, GLOW, GHIGH, ODONOLOWCRASH = .TRUE. )
    ELSE
      CALL TPFLYER%COMPUTE_VERTICAL_INTERP_COEFF( 'MASS', TPFLYER%XZ_CUR, ZZM,  GLOW, GHIGH, ODONOLOWCRASH = .TRUE. )
    END IF

  CLASS IS ( TBALLOONDATA )
    IF ( TPFLYER%CTYPE == 'ISODEN' ) THEN
      CALL TPFLYER%COMPUTE_VERTICAL_INTERP_COEFF( 'MASS', TPFLYER%XRHO,   ZRHO, GLOW, GHIGH, ODONOLOWCRASH = .TRUE. )
    ELSE IF ( TPFLYER%CTYPE == 'RADIOS' .OR. TPFLYER%CTYPE == 'CVBALL' ) THEN
      CALL TPFLYER%COMPUTE_VERTICAL_INTERP_COEFF( 'MASS', TPFLYER%XZ_CUR, ZZM,  GLOW, GHIGH, ODONOLOWCRASH = .TRUE. )
    END IF

END SELECT

! Check if the flyer crashed vertically (higher bound)
IF ( GHIGH ) THEN
  TPFLYER%LCRASH = .TRUE.
  TPFLYER%NCRASH = NCRASH_OUT_HIGH

  SELECT TYPE ( TPFLYER )
    CLASS IS ( TAIRCRAFTDATA )
      WRITE( CMNHMSG(1), "( 'aircraft ', A, ' crashed on ', I2, '/', I2, '/', I4, ' at ', F18.12, 's (too  high)' )" ) &
             TRIM( TPFLYER%CNAME ), TDTCUR%NDAY, TDTCUR%NMONTH, TDTCUR%NYEAR, TDTCUR%XTIME
    CLASS IS ( TBALLOONDATA )
      WRITE( CMNHMSG(1), "( 'balloon ', A, ' crashed on ', I2, '/', I2, '/', I4, ' at ', F18.12, 's (too  high)' )" ) &
             TRIM( TPFLYER%CNAME ), TDTCUR%NDAY, TDTCUR%NMONTH, TDTCUR%NYEAR, TDTCUR%XTIME
  END SELECT
  CALL PRINT_MSG( NVERB_WARNING, 'GEN', 'AIRCRAFT_BALLOON_EVOL', OLOCAL = .TRUE. )
END IF

SELECT TYPE ( TPFLYER )
  CLASS IS ( TAIRCRAFTDATA)
    IF ( TPFLYER%LALTDEF ) THEN
      TPFLYER%XZ_CUR = TPFLYER%INTERP_FROM_MASSPOINT( ZZM )
    ELSE
      TPFLYER%XP_CUR = TPFLYER%INTERP_FROM_MASSPOINT( PP )
    END IF

  CLASS IS ( TBALLOONDATA)
    IF ( TPFLYER%CTYPE == 'ISODEN' ) THEN
      TPFLYER%XZ_CUR = TPFLYER%INTERP_FROM_MASSPOINT( ZZM )
    ELSE IF ( TPFLYER%CTYPE == 'RADIOS' .OR. TPFLYER%CTYPE == 'CVBALL' ) THEN
      !Nothing to do
    END IF

END SELECT

END SUBROUTINE FLYER_COMPUTE_INTERP_COEFF_VER
!----------------------------------------------------------------------------
!----------------------------------------------------------------------------
SUBROUTINE FLYER_COMPUTE_INTERP_COEFF_HOR_STAGE2( )
! Compute coefficents for horizontal interpolations (2nd stage)
! This stage must be done after FLYER_COMPUTE_INTERP_COEFF_VER because we should need XZ_CUR computed in it

IMPLICIT NONE

LOGICAL :: GLOW, GHIGH

! Interpolation coefficients for the 4 surroundings verticals (for U)
! ODONOLOWCRASH = .TRUE. because check for low crash has already been done
CALL TPFLYER%COMPUTE_VERTICAL_INTERP_COEFF( 'U', TPFLYER%XZ_CUR, ZZU, GLOW, GHIGH, ODONOLOWCRASH = .TRUE. )

! Interpolation coefficients for the 4 suroundings verticals (for V)
CALL TPFLYER%COMPUTE_VERTICAL_INTERP_COEFF( 'V', TPFLYER%XZ_CUR, ZZV, GLOW, GHIGH, ODONOLOWCRASH = .TRUE. )

END SUBROUTINE FLYER_COMPUTE_INTERP_COEFF_HOR_STAGE2
!----------------------------------------------------------------------------
!----------------------------------------------------------------------------
SUBROUTINE FLYER_RECORD_DATA( )

USE MODD_CST,              ONLY: XP00, XPI, XRD
USE MODD_DIAG_IN_RUN,      ONLY: XCURRENT_TKE_DISS
USE MODD_GRID,             ONLY: XBETA, XLON0, XRPK
USE MODD_NSV,              ONLY: NSV_LIMA_NC, NSV_LIMA_NR, NSV_LIMA_NI
USE MODD_PARAMETERS,       ONLY: JPVEXT
USE MODD_PARAM_n,          ONLY: CCLOUD, CRAD

USE MODE_GRIDPROJ,         ONLY: SM_LATLON
USE MODE_SENSOR,           ONLY: Sensor_rare_compute, Sensor_wc_compute

IMPLICIT NONE

INTEGER                        :: JLOOP    ! loop counter
REAL                           :: ZGAM     ! rotation between meso-nh base and spherical lat-lon base.
REAL                           :: ZU_BAL   ! horizontal wind speed at balloon location (along x)
REAL                           :: ZV_BAL   ! horizontal wind speed at balloon location (along y)
REAL, DIMENSION(SIZE(PZ,3))    :: ZZ       ! altitude of model levels at station location
REAL, DIMENSION(SIZE(PR,1),SIZE(PR,2),SIZE(PR,3))    :: ZR

TPFLYER%NMODELHIST(ISTORE) = TPFLYER%NMODEL

TPFLYER%XX(ISTORE) = TPFLYER%XX_CUR
TPFLYER%XY(ISTORE) = TPFLYER%XY_CUR
TPFLYER%XZ(ISTORE) = TPFLYER%XZ_CUR
!
CALL SM_LATLON( PLATOR, PLONOR,                    &
                TPFLYER%XX_CUR,   TPFLYER%XY_CUR,  &
                TPFLYER%XLAT_CUR, TPFLYER%XLON_CUR )
TPFLYER%XLAT(ISTORE) = TPFLYER%XLAT_CUR
TPFLYER%XLON(ISTORE) = TPFLYER%XLON_CUR
!
ZU_BAL = TPFLYER%INTERP_FROM_UPOINT( PU )
ZV_BAL = TPFLYER%INTERP_FROM_VPOINT( PV )
ZGAM   = (XRPK * (TPFLYER%XLON_CUR - XLON0) - XBETA)*(XPI/180.)
TPFLYER%XZON (1,ISTORE) = ZU_BAL * COS(ZGAM) + ZV_BAL * SIN(ZGAM)
TPFLYER%XMER (1,ISTORE) = - ZU_BAL * SIN(ZGAM) + ZV_BAL * COS(ZGAM)
!
TPFLYER%XW   (1,ISTORE) = TPFLYER%INTERP_FROM_MASSPOINT( ZWM )
TPFLYER%XTH  (1,ISTORE) = TPFLYER%INTERP_FROM_MASSPOINT( PTH )
!
ZFLYER_EXN = TPFLYER%INTERP_FROM_MASSPOINT( ZEXN )
TPFLYER%XP   (1,ISTORE) = XP00 * ZFLYER_EXN**(XCPD/XRD)

ZR(:,:,:) = 0.
DO JLOOP=1,SIZE(PR,4)
  TPFLYER%XR   (1,ISTORE,JLOOP) = TPFLYER%INTERP_FROM_MASSPOINT( PR(:,:,:,JLOOP) )
  IF (JLOOP>=2) ZR(:,:,:) = ZR(:,:,:) + PR(:,:,:,JLOOP)
END DO
DO JLOOP=1,SIZE(PSV,4)
  TPFLYER%XSV  (1,ISTORE,JLOOP) = TPFLYER%INTERP_FROM_MASSPOINT( PSV(:,:,:,JLOOP) )
END DO
TPFLYER%XRTZ  (:,ISTORE) = TPFLYER%INTERP_HOR_FROM_MASSPOINT( ZR(:,:,:) )
DO JLOOP=1,SIZE(PR,4)
  TPFLYER%XRZ  (:,ISTORE,JLOOP) = TPFLYER%INTERP_HOR_FROM_MASSPOINT( PR(:,:,:,JLOOP) )
END DO

TPFLYER%XFFZ  (:,ISTORE) = TPFLYER%INTERP_HOR_FROM_MASSPOINT( SQRT(PU**2+PV**2) )

TPFLYER%XRHOD (:,ISTORE) = TPFLYER%INTERP_HOR_FROM_MASSPOINT( PRHODREF )

IF (CCLOUD=="LIMA") THEN
  TPFLYER%XCIZ  (:,ISTORE) = TPFLYER%INTERP_HOR_FROM_MASSPOINT( PSV(:,:,:,NSV_LIMA_NI) )
  TPFLYER%XCCZ  (:,ISTORE) = TPFLYER%INTERP_HOR_FROM_MASSPOINT( PSV(:,:,:,NSV_LIMA_NC) )
  TPFLYER%XCRZ  (:,ISTORE) = TPFLYER%INTERP_HOR_FROM_MASSPOINT( PSV(:,:,:,NSV_LIMA_NR) )
ELSE IF ( CCLOUD=="ICE3" .OR. CCLOUD=="ICE4" ) THEN
  TPFLYER%XCIZ  (:,ISTORE) = TPFLYER%INTERP_HOR_FROM_MASSPOINT( PCIT(:,:,:) )
END IF

ZTH_EXN(:,:,:) = PTH(TPFLYER%NI_M:TPFLYER%NI_M+1, TPFLYER%NJ_M:TPFLYER%NJ_M+1, :) * ZEXN(:,:,:)
ZZ(:) = TPFLYER%INTERP_HOR_FROM_MASSPOINT( ZZM(:,:,:) )
TPFLYER%XZZ(:,ISTORE) = ZZ(:)

CALL Sensor_wc_compute(   TPFLYER, ISTORE, PR, PRHODREF )
CALL Sensor_rare_compute( TPFLYER, ISTORE, PR, PSV, PRHODREF, PCIT, ZTH_EXN, ZZ, PSEA )

! vertical wind
TPFLYER%XWZ  (:,ISTORE) = TPFLYER%INTERP_HOR_FROM_MASSPOINT( ZWM(:,:,:) )

! Dry air density at flyer position
TPFLYER%XRHOD_SENSOR(ISTORE) = TPFLYER%INTERP_FROM_MASSPOINT( PRHODREF )

IF (SIZE(PTKE)>0) TPFLYER%XTKE  (1,ISTORE) = TPFLYER%INTERP_FROM_MASSPOINT( PTKE )
IF ( CRAD /= 'NONE' ) TPFLYER%XTSRAD(ISTORE) = TPFLYER%INTERP_HOR_FROM_MASSPOINT(PTS )
TPFLYER%XTKE_DISS(ISTORE) = TPFLYER%INTERP_FROM_MASSPOINT( XCURRENT_TKE_DISS )
TPFLYER%XZS(ISTORE)       = TPFLYER%INTERP_HOR_FROM_MASSPOINT( PZ(:,:,1+JPVEXT) )
TPFLYER%XTHW_FLUX(ISTORE) = TPFLYER%INTERP_FROM_MASSPOINT( ZTHW_FLUX )
TPFLYER%XRCW_FLUX(ISTORE) = TPFLYER%INTERP_FROM_MASSPOINT( ZRCW_FLUX )
DO JLOOP=1,SIZE(PSV,4)
TPFLYER%XSVW_FLUX(ISTORE,JLOOP) = TPFLYER%INTERP_FROM_MASSPOINT( ZSVW_FLUX(:,:,:,JLOOP) )
END DO

END SUBROUTINE FLYER_RECORD_DATA
!----------------------------------------------------------------------------
!----------------------------------------------------------------------------
END SUBROUTINE AIRCRAFT_BALLOON_EVOL
!----------------------------------------------------------------------------
!----------------------------------------------------------------------------
SUBROUTINE AIRCRAFT_COMPUTE_POSITION( TPDATE, TPAIRCRAFT )

USE MODD_AIRCRAFT_BALLOON, ONLY: TAIRCRAFTDATA
USE MODD_TYPE_DATE,        ONLY: DATE_TIME

USE MODE_DATETIME
USE MODE_POSITION_TOOLS,   ONLY: FIND_PROCESS_AND_MODEL_FROM_XY_POS

IMPLICIT NONE

TYPE(DATE_TIME),      INTENT(IN)    :: TPDATE
CLASS(TAIRCRAFTDATA), INTENT(INOUT) :: TPAIRCRAFT !aircraft

INTEGER :: IL        ! flight segment index
REAL    :: ZTDIST    ! time since launch (sec)
REAL    :: ZSEG_FRAC ! fraction of flight in the current segment

! Find the flight segment
ZTDIST = TPDATE - TPAIRCRAFT%TLAUNCH
IL = TPAIRCRAFT%NPOSCUR
DO WHILE ( ZTDIST > TPAIRCRAFT%XPOSTIME(IL+1) )
  IL = IL + 1
  IF ( IL > TPAIRCRAFT%NPOS-1 ) THEN
    !Security (should not happen)
    IL = TPAIRCRAFT%NPOS-1
    EXIT
  END IF
END DO
TPAIRCRAFT%NPOSCUR = IL

! Compute the current position
ZSEG_FRAC = ( ZTDIST - TPAIRCRAFT%XPOSTIME(IL) ) / ( TPAIRCRAFT%XPOSTIME(IL+1) - TPAIRCRAFT%XPOSTIME(IL) )

TPAIRCRAFT%XX_CUR = (1.-ZSEG_FRAC) * TPAIRCRAFT%XPOSX(IL  ) &
               +     ZSEG_FRAC  * TPAIRCRAFT%XPOSX(IL+1)
TPAIRCRAFT%XY_CUR = (1.-ZSEG_FRAC) * TPAIRCRAFT%XPOSY(IL  ) &
               +     ZSEG_FRAC  * TPAIRCRAFT%XPOSY(IL+1)

IF (TPAIRCRAFT%LALTDEF) THEN
  TPAIRCRAFT%XP_CUR = (1.-ZSEG_FRAC) * TPAIRCRAFT%XPOSP(IL  ) &
                 +     ZSEG_FRAC  * TPAIRCRAFT%XPOSP(IL+1)
ELSE
  TPAIRCRAFT%XZ_CUR = (1.-ZSEG_FRAC) * TPAIRCRAFT%XPOSZ(IL ) &
                 +     ZSEG_FRAC  * TPAIRCRAFT%XPOSZ(IL +1)
END IF

END SUBROUTINE AIRCRAFT_COMPUTE_POSITION
!----------------------------------------------------------------------------
!----------------------------------------------------------------------------
SUBROUTINE FLYER_GET_RANK_MODEL_ISCRASHED( TPFLYER, PX, PY, KMODEL )

USE MODD_AIRCRAFT_BALLOON, ONLY: NCRASH_NO, NCRASH_OUT_HORIZ, TFLYERDATA

USE MODE_POSITION_TOOLS,   ONLY: FIND_PROCESS_AND_MODEL_FROM_XY_POS

IMPLICIT NONE

CLASS(TFLYERDATA), INTENT(INOUT) :: TPFLYER ! balloon/aircraft
REAL,    OPTIONAL, INTENT(IN)    :: PX      ! X position (if not provided, takes current flyer position)
REAL,    OPTIONAL, INTENT(IN)    :: PY      ! Y position (if not provided, takes current flyer position)
INTEGER, OPTIONAL, INTENT(IN)    :: KMODEL  ! if provided, model number is imposed (if not 0)

INTEGER :: IMODEL
INTEGER :: IRANK
REAL    :: ZX, ZY

IF ( PRESENT( KMODEL ) ) THEN
  IMODEL = KMODEL
ELSE
  IF ( TPFLYER%CMODEL == 'FIX' ) THEN
    IMODEL = TPFLYER%NMODEL
  ELSE
    IMODEL = 0
  END IF
END IF

IF ( PRESENT( PX ) ) THEN
  ZX = PX
ELSE
  ZX = TPFLYER%XX_CUR
END IF

IF ( PRESENT( PY ) ) THEN
  ZY = PY
ELSE
  ZY = TPFLYER%XY_CUR
END IF

CALL FIND_PROCESS_AND_MODEL_FROM_XY_POS( ZX, ZY, IRANK, IMODEL )

IF ( IRANK < 1 ) THEN
  ! Flyer is outside of horizontal domain
  ! TPFLYER%NMODEL !Do not change to keep a valid value
  TPFLYER%LCRASH    = .TRUE.
  TPFLYER%NCRASH    = NCRASH_OUT_HORIZ
  TPFLYER%LFLY      = .FALSE.
ELSE
  TPFLYER%NMODEL    = IMODEL
  TPFLYER%LCRASH    = .FALSE.
  TPFLYER%NCRASH    = NCRASH_NO
  !TPFLYER%LFLY      = !Do not touch LFLY (flyer could be in flight or not)
  TPFLYER%NRANK_CUR = IRANK
END IF

END SUBROUTINE FLYER_GET_RANK_MODEL_ISCRASHED
!----------------------------------------------------------------------------

END MODULE MODE_AIRCRAFT_BALLOON_EVOL