LCOV - code coverage report
Current view: top level - src - mo_mad.f90 (source / functions) Hit Total Coverage
Test: forces coverage Lines: 0 124 0.0 %
Date: 2024-03-13 19:03:28 Functions: 0 4 0.0 %

          Line data    Source code
       1             : !> \file mo_mad.f90
       2             : !> \brief \copybrief mo_mad
       3             : !> \details \copydetails mo_mad
       4             : 
       5             : !> \brief Median absolute deviation test.
       6             : !> \details This module provides a median absolute deviation (MAD) test.
       7             : !> \authors Matthias Cuntz, Mathias Zink
       8             : !> \date 2011 - 2012
       9             : !> \copyright Copyright 2005-\today, the CHS Developers, Sabine Attinger: All rights reserved.
      10             : !! FORCES is released under the LGPLv3+ license \license_note
      11             : MODULE mo_mad
      12             : 
      13             :   USE mo_kind,       ONLY: i4, sp, dp
      14             :   USE mo_percentile, ONLY: median
      15             : 
      16             :   Implicit NONE
      17             : 
      18             :   PUBLIC :: mad             ! Mean absolute deviation test
      19             : 
      20             :   ! ------------------------------------------------------------------
      21             : 
      22             :   !>       \brief Mean absolute deviation test.
      23             : 
      24             :   !>       \details Mean absolute deviation test with optional z-value (default: 7) and mask,
      25             :   !!       and 3 optional variants:
      26             :   !!
      27             :   !!         - 0: raw values (default)
      28             :   !!         - 1: 1st derivative
      29             :   !!         - 2: 2nd derivative
      30             :   !!
      31             :   !!       Returns mask with true everywhere except where `0<(median-MAD*z/0.6745)` or `>(md+MAD*z/0.6745)` \n
      32             :   !!
      33             :   !!       If an optional mask is given, the mad test is only performed on those locations that correspond
      34             :   !!       to true values in the mask.\n
      35             :   !!
      36             :   !!       If tout is given mad returns the array with the enteries exceeding the treshold
      37             :   !!       being set to the threshold. With this setting arrays are accepted. \n
      38             :   !!       tout accepts:
      39             :   !!
      40             :   !!         - u: upper values are cut at the threshold
      41             :   !!         - l: lower values are cut at the threshold
      42             :   !!         - b: upper and lower values are cut at the threshold
      43             :   !!
      44             :   !!       With this setting only the variant 0 is available (no argument implemented).
      45             :   !!
      46             :   !!       \b Example
      47             :   !!
      48             :   !!       \code{.f90}
      49             :   !!             vec = (/ -0.25,0.68,0.94,1.15,2.26,2.35,2.37,2.40,2.47,2.54,2.62, &
      50             :   !!                       2.64,2.90,2.92,2.92,2.93,3.21,3.26,3.30,3.59,3.68,4.30, &
      51             :   !!                       4.64,5.34,5.42,8.01 /)
      52             :   !!             mask(:) = true
      53             :   !!             ! Sets last entry false
      54             :   !!             mask = mask .and. mad(vec, z=4., deriv=0, mask=mask)
      55             :   !!       \endcode
      56             :   !!
      57             :   !!       See also example in test directory.
      58             : 
      59             : 
      60             :   !>       \param[in]  "real(sp/dp) :: vec(:)"               1D-array with input numbers
      61             :   !>       \param[in]  "real(sp/dp) :: arr"                  nD-array with input numbers
      62             :   !>       \param[in]  "real(sp/dp), optional :: z"          Input is allowed to deviate maximum z standard deviations
      63             :   !!                                                         from the median (default: 7).
      64             :   !>       \param[in]  "integer(i4), optional :: deriv"      0: Act on raw input (default: 0)\n
      65             :   !!                                                         1: Use first derivatives\n
      66             :   !!                                                         2: Use 2nd derivatives
      67             :   !>       \param[in]  "logical, optional     :: mask(:)"    1D-array of logical values with size(vec).
      68             :   !!                                                         If present, only those locations in vec corresponding to the true
      69             :   !!                                                         values in mask are used. nD-array if tout is used.
      70             :   !>       \param[in]  "character(1):: tout"                 u: Trim only values above mad\n
      71             :   !!                                                         l: Trim only values below mad\n
      72             :   !!                                                          b: Trim values below and above mad
      73             : 
      74             :   !>       \retval "logical or real(sp/dp), dimension(size(arr)) :: out"        mask with true everywhere except where input
      75             :   !!                                                                            deviates more than z standard deviations from
      76             :   !!                                                                            median. If tout five, threshold value is returned.
      77             : 
      78             :   !>     \note
      79             :   !!           1st derivative is
      80             :   !!
      81             :   !!               d = datin[1:n]-datin[0:n-1]
      82             :   !!
      83             :   !!           because mean of left and right would give 0 for spikes.
      84             :   !!
      85             :   !>        \author Matthias Cuntz
      86             :   !>        \date Mar 2011
      87             : 
      88             :   !>        \author Mathhias Kelbling
      89             :   !>        \date May 2018
      90             :   !!          - mad_val added by Matthias Kelbling, May 2018
      91             : 
      92             :   ! ------------------------------------------------------------------
      93             : 
      94             :   INTERFACE mad
      95             :      MODULE PROCEDURE mad_sp, mad_dp, mad_val_dp, mad_val_sp
      96             :   END INTERFACE mad
      97             : 
      98             :   PRIVATE
      99             : 
     100             :   ! ------------------------------------------------------------------
     101             : 
     102             : CONTAINS
     103             : 
     104             :   ! ------------------------------------------------------------------
     105             : 
     106           0 :   FUNCTION mad_dp(arr, z, mask, deriv)
     107             : 
     108             :     IMPLICIT NONE
     109             : 
     110             :     REAL(dp),    DIMENSION(:),           INTENT(IN) :: arr
     111             :     REAL(dp),                  OPTIONAL, INTENT(IN) :: z
     112             :     LOGICAL,     DIMENSION(:), OPTIONAL, INTENT(IN) :: mask
     113             :     INTEGER(i4),               OPTIONAL, INTENT(IN) :: deriv
     114             :     LOGICAL,     DIMENSION(size(arr))               :: mad_dp
     115             : 
     116           0 :     LOGICAL,  DIMENSION(size(arr)) :: maske
     117           0 :     REAL(dp), DIMENSION(size(arr)) :: d
     118           0 :     LOGICAL,  DIMENSION(size(arr)) :: dmask
     119             :     INTEGER(i4) :: n, ideriv
     120             :     !INTEGER(i4) :: m
     121           0 :     REAL(dp)    :: iz, med, mabsdev, thresh
     122             : 
     123           0 :     n = size(arr)
     124           0 :     maske(:) = .true.
     125           0 :     if (present(mask)) then
     126           0 :        if (size(mask) /= n) stop 'Error mad_dp: size(mask) /= size(arr)'
     127           0 :        maske = mask
     128             :     endif
     129           0 :     if (present(z)) then
     130           0 :        iz = z
     131             :     else
     132             :        iz = 7.0_dp
     133             :     endif
     134           0 :     if (present(deriv)) then
     135           0 :        ideriv = deriv
     136             :     else
     137             :        ideriv = 0
     138             :     endif
     139             : 
     140           0 :     select case(ideriv)
     141             :     case(0) ! pure values
     142             :        !m       = count(maske)
     143           0 :        med     = median(arr,mask=maske)
     144           0 :        mabsdev = median(abs(arr-med),mask=maske)
     145           0 :        thresh  = mabsdev * iz/0.6745_dp
     146           0 :        mad_dp     = (arr .ge. (med-thresh)) .and. (arr .le. (med+thresh)) .and. maske
     147             :     case(1) ! 1st derivative
     148             :        ! d(1:n-2) = 0.5_dp* (arr(3:n) - arr(1:n-2)) ! does not work -> ask Clemens & Matthias M
     149           0 :        d(1:n-1)     = arr(2:n) - arr(1:n-1)
     150           0 :        dmask(1:n-1) = maske(2:n) .and. maske(1:n-1)
     151             :        !m            = count(dmask(1:n-1))
     152           0 :        med          = median(d(1:n-1),mask=dmask(1:n-1))
     153           0 :        mabsdev      = median(abs(d(1:n-1)-med),mask=dmask(1:n-1))
     154           0 :        thresh       = mabsdev * iz/0.6745_dp
     155           0 :        mad_dp(n)       = .true.
     156           0 :        mad_dp(1:n-1)   = (d(1:n-1) .ge. (med-thresh)) .and. (d(1:n-1) .le. (med+thresh)) .and. dmask(1:n-1)
     157             :     case(2) ! -2nd derivative
     158           0 :        d(1:n-2)     = arr(2:n-1) + arr(2:n-1) - arr(1:n-2) - arr(3:n)
     159           0 :        dmask(1:n-2) = maske(2:n-1) .and. maske(1:n-2) .and. maske(3:n)
     160             :        !m            = count(dmask(1:n-2))
     161           0 :        med          = median(d(1:n-2),mask=dmask(1:n-2))
     162           0 :        mabsdev      = median(abs(d(1:n-2)-med),mask=dmask(1:n-2))
     163           0 :        thresh       = mabsdev * iz/0.6745_dp
     164           0 :        mad_dp(1)       = .true.
     165           0 :        mad_dp(n)       = .true.
     166           0 :        mad_dp(2:n-1)   = (d(1:n-2) .ge. (med-thresh)) .and. (d(1:n-2) .le. (med+thresh)) .and. dmask(1:n-2)
     167             :     case default
     168           0 :        stop 'Unimplemented option in mad_dp'
     169             :     end select
     170             : 
     171           0 :   END FUNCTION mad_dp
     172             : 
     173             : 
     174           0 :   FUNCTION mad_sp(arr, z, mask, deriv)
     175             : 
     176             :     IMPLICIT NONE
     177             : 
     178             :     REAL(sp),    DIMENSION(:),           INTENT(IN) :: arr
     179             :     REAL(sp),                  OPTIONAL, INTENT(IN) :: z
     180             :     LOGICAL,     DIMENSION(:), OPTIONAL, INTENT(IN) :: mask
     181             :     INTEGER(i4),               OPTIONAL, INTENT(IN) :: deriv
     182             :     LOGICAL,     DIMENSION(size(arr))               :: mad_sp
     183             : 
     184           0 :     LOGICAL,  DIMENSION(size(arr)) :: maske
     185           0 :     REAL(sp), DIMENSION(size(arr)) :: d
     186           0 :     LOGICAL,  DIMENSION(size(arr)) :: dmask
     187             :     INTEGER(i4) :: n, ideriv
     188             :     !INTEGER(i4) :: m
     189           0 :     REAL(sp)    :: iz, med, mabsdev, thresh
     190             : 
     191           0 :     n = size(arr)
     192           0 :     maske(:) = .true.
     193           0 :     if (present(mask)) then
     194           0 :        if (size(mask) /= n) stop 'Error mad_sp: size(mask) /= size(arr)'
     195           0 :        maske = mask
     196             :     endif
     197           0 :     if (present(z)) then
     198           0 :        iz = z
     199             :     else
     200             :        iz = 7.0_sp
     201             :     endif
     202           0 :     if (present(deriv)) then
     203           0 :        ideriv = deriv
     204             :     else
     205             :        ideriv = 0
     206             :     endif
     207             : 
     208           0 :     select case(ideriv)
     209             :     case(0) ! pure values
     210             :        !m       = count(maske)
     211           0 :        med     = median(arr,mask=maske)
     212           0 :        mabsdev = median(abs(arr-med),mask=maske)
     213           0 :        thresh  = mabsdev * iz/0.6745_sp
     214           0 :        mad_sp     = (arr .ge. (med-thresh)) .and. (arr .le. (med+thresh)) .and. maske
     215             :     case(1) ! 1st derivative
     216             :        ! d(1:n-2) = 0.5_sp* (arr(3:n) - arr(1:n-2)) ! does not work -> ask Clemens & Matthias M
     217           0 :        d(1:n-1)     = arr(2:n) - arr(1:n-1)
     218           0 :        dmask(1:n-1) = maske(2:n) .and. maske(1:n-1)
     219             :        !m            = count(dmask(1:n-1))
     220           0 :        med          = median(d(1:n-1),mask=dmask(1:n-1))
     221           0 :        mabsdev      = median(abs(d(1:n-1)-med),mask=dmask(1:n-1))
     222           0 :        thresh       = mabsdev * iz/0.6745_sp
     223           0 :        mad_sp(n)       = .true.
     224           0 :        mad_sp(1:n-1)   = (d(1:n-1) .ge. (med-thresh)) .and. (d(1:n-1) .le. (med+thresh)) .and. dmask(1:n-1)
     225             :     case(2) ! -2nd derivative
     226           0 :        d(1:n-2)     = arr(2:n-1) + arr(2:n-1) - arr(1:n-2) - arr(3:n)
     227           0 :        dmask(1:n-2) = maske(2:n-1) .and. maske(1:n-2) .and. maske(3:n)
     228             :        !m            = count(dmask(1:n-2))
     229           0 :        med          = median(d(1:n-2),mask=dmask(1:n-2))
     230           0 :        mabsdev      = median(abs(d(1:n-2)-med),mask=dmask(1:n-2))
     231           0 :        thresh       = mabsdev * iz/0.6745_sp
     232           0 :        mad_sp(1)       = .true.
     233           0 :        mad_sp(n)       = .true.
     234           0 :        mad_sp(2:n-1)   = (d(1:n-2) .ge. (med-thresh)) .and. (d(1:n-2) .le. (med+thresh)) .and. dmask(1:n-2)
     235             :     case default
     236           0 :        stop 'Unimplemented option in mad_sp'
     237             :     end select
     238             : 
     239           0 :   END FUNCTION mad_sp
     240             : 
     241             :   ! ------------------------------------------------------------------
     242             : 
     243           0 :   FUNCTION mad_val_dp(arr, z, mask, tout, mval)
     244             : 
     245             :     IMPLICIT NONE
     246             : 
     247             :     REAL(dp),    DIMENSION(:),           INTENT(IN) :: arr
     248             :     REAL(dp),                  OPTIONAL, INTENT(IN) :: z, mval
     249             :     LOGICAL,     DIMENSION(:), OPTIONAL, INTENT(IN) :: mask
     250             :     REAL(dp),    DIMENSION(size(arr))               :: mad_val_dp
     251             :     CHARACTER(1)                                    :: tout ! type out
     252             :     ! u : cut upper; l : cut lower; b : cut upper and lower
     253             : 
     254           0 :     LOGICAL,  DIMENSION(size(arr)) :: maske
     255             :     INTEGER(i4) :: n
     256           0 :     REAL(dp)    :: iz, med, mabsdev, thresh
     257             : 
     258           0 :     n = size(arr)
     259           0 :     maske(:) = .true.
     260           0 :     if (present(mask)) then
     261           0 :        if (size(mask) /= n) stop 'Error mad_val_dp: size(mask) /= size(arr)'
     262           0 :        maske = mask
     263             :     endif
     264           0 :     if (present(z)) then
     265           0 :        iz = z
     266             :     else
     267             :        iz = 7.0_dp
     268             :     endif
     269             : 
     270           0 :     if (present(mval)) then
     271           0 :        where (abs(arr - mval) .lt. tiny(1._dp) ) maske = .false.
     272             :        ! reset if no values remain
     273           0 :        if (.not. any(maske)) then
     274           0 :           where ( abs(arr - mval) .lt. tiny(1._dp) ) maske = .true.
     275             :        end if
     276             :     endif
     277             : 
     278           0 :     med     = median(arr,mask=maske)
     279           0 :     mabsdev = median(abs(arr-med),mask=maske)
     280           0 :     thresh  = mabsdev * iz/0.6745_dp
     281           0 :     mad_val_dp = arr
     282             : 
     283             :     select case(tout)
     284             :     case("u")
     285             :       ! print *, "The threshold is set to", med, "+", thresh
     286             :       where ((mad_val_dp .gt. (med+thresh)) &
     287           0 :            .and. maske) mad_val_dp = med+thresh
     288             :     case("l")
     289             :       ! print *, "The threshold is set to", med, "-", thresh
     290             :       where ((mad_val_dp .lt. (med-thresh)) &
     291           0 :            .and. maske) mad_val_dp = med-thresh
     292             :     case("b")
     293             :       ! print *, "The threshold is set to", med, "+/-", thresh
     294             :       where ((mad_val_dp .gt. (med+thresh)) &
     295           0 :            .and. maske) mad_val_dp = med+thresh
     296             :       where ((mad_val_dp .lt. (med-thresh)) &
     297           0 :            .and. maske) mad_val_dp = med-thresh
     298             :     case default
     299           0 :        stop 'Unimplemented option in mad_val_dp'
     300             :     end select
     301             : 
     302           0 :   END FUNCTION mad_val_dp
     303             : 
     304             :   ! ------------------------------------------------------------------
     305             : 
     306           0 :   FUNCTION mad_val_sp(arr, z, mask, tout, mval)
     307             : 
     308             :     IMPLICIT NONE
     309             : 
     310             :     REAL(sp),    DIMENSION(:),           INTENT(IN) :: arr
     311             :     REAL(sp),                  OPTIONAL, INTENT(IN) :: z, mval
     312             :     LOGICAL,     DIMENSION(:), OPTIONAL, INTENT(IN) :: mask
     313             :     REAL(sp),    DIMENSION(size(arr))               :: mad_val_sp
     314             :     CHARACTER(1)                                    :: tout ! type out
     315             :     ! u : cut upper; l : cut lower; b : cut upper and lower
     316             : 
     317           0 :     LOGICAL,  DIMENSION(size(arr)) :: maske
     318             :     INTEGER(i4) :: n
     319           0 :     REAL(sp)    :: iz, med, mabsdev, thresh
     320             : 
     321           0 :     n = size(arr)
     322           0 :     maske(:) = .true.
     323           0 :     if (present(mask)) then
     324           0 :        if (size(mask) /= n) stop 'Error mad_val_sp: size(mask) /= size(arr)'
     325           0 :        maske = mask
     326             :     endif
     327           0 :     if (present(z)) then
     328           0 :        iz = z
     329             :     else
     330             :        iz = 7.0_sp
     331             :     endif
     332             : 
     333           0 :     if (present(mval)) then
     334           0 :        where (abs(arr - mval) .lt. tiny(1._sp)) maske = .false.
     335             :        ! reset if no values remain
     336           0 :        if (.not. any(maske)) then
     337           0 :           where ( abs(arr - mval) .lt. tiny(1._dp) ) maske = .true.
     338             :        end if
     339             :     endif
     340             : 
     341           0 :     med     = median(arr,mask=maske)
     342           0 :     mabsdev = median(abs(arr-med),mask=maske)
     343           0 :     thresh  = mabsdev * iz/0.6745_sp
     344           0 :     mad_val_sp = arr
     345           0 :     select case(tout)
     346             :     case("u")
     347           0 :       print *, "The threshold is set to", med, "+", thresh
     348             :       where ((mad_val_sp .gt. (med+thresh)) &
     349           0 :            .and. maske) mad_val_sp = med+thresh
     350             :     case("l")
     351           0 :       print *, "The threshold is set to", med, "-", thresh
     352             :       where ((mad_val_sp .lt. (med-thresh)) &
     353           0 :            .and. maske) mad_val_sp = med-thresh
     354             :     case("b")
     355           0 :       print *, "The threshold is set to", med, "+/-", thresh
     356             :       where ((mad_val_sp .gt. (med+thresh)) &
     357           0 :            .and. maske) mad_val_sp = med+thresh
     358             :       where ((mad_val_sp .lt. (med-thresh)) &
     359           0 :            .and. maske) mad_val_sp = med-thresh
     360             :     case default
     361           0 :        stop 'Unimplemented option in mad_val_sp'
     362             :     end select
     363             : 
     364           0 :   END FUNCTION mad_val_sp
     365             : 
     366             :   ! ------------------------------------------------------------------
     367             : 
     368             : 
     369             : END MODULE mo_mad

Generated by: LCOV version 1.16