LCOV - code coverage report
Current view: top level - src - mo_boxcox.f90 (source / functions) Hit Total Coverage
Test: forces coverage Lines: 78 79 98.7 %
Date: 2024-03-13 19:03:28 Functions: 6 6 100.0 %

          Line data    Source code
       1             : !> \file mo_boxcox.f90
       2             : !> \brief \copybrief mo_boxcox
       3             : !> \details \copydetails mo_boxcox
       4             : 
       5             : !> \brief Box-Cox transformation of data.
       6             : !> \details This module contains routines to calculate the Box-Cox transformation
       7             : !!          as well as estimating the best exponent for the Box-Cox transformation
       8             : !> \changelog
       9             : !! - March 2011, Matthias Cuntz
      10             : !!   - modified Python code of Travis Oliphant (2002): boxcox, llf_boxcox
      11             : !! - Dec 2019: Robert Schweppe:
      12             : !!   - removed NR code (get_boxcox)
      13             : !> \author Mathias Cuntz
      14             : !> \date Aug 2011
      15             : !> \copyright Copyright 2005-\today, the CHS Developers, Sabine Attinger: All rights reserved.
      16             : !! FORCES is released under the LGPLv3+ license \license_note
      17             : MODULE mo_boxcox
      18             : 
      19             :   USE mo_kind, ONLY : sp, dp
      20             :   USE mo_utils, only : le
      21             : 
      22             :   IMPLICIT NONE
      23             : 
      24             :   PUBLIC :: boxcox
      25             :   PUBLIC :: invboxcox
      26             : 
      27             :   ! ------------------------------------------------------------------
      28             : 
      29             :   !>        \brief Transform a positive dataset with a Box-Cox power transformation.
      30             : 
      31             :   !>        \details Calculate Box-Cox power transformed values given the original values and the exponent lambda.\n
      32             :   !!          \f[ w_\text{Box-Cox}(x) =
      33             :   !!                          \begin{cases}
      34             :   !!                           \frac{x^\lambda - 1}{\lambda}&\text{, if }\lambda \neq 0 \\
      35             :   !!                           \ln{x}&\text{, if }\lambda = 0
      36             :   !!                          \end{cases} \f]
      37             :   !!         If an optional mask is given, then the Box-Cox transformation is only performed on
      38             :   !!         those locations that correspond to true values in the mask.\n
      39             :   !!         \f$x\f$ can be single or double precision. The result will have the same numerical precision.
      40             :   !!         \f$x\f$ can be scalar or vector.\n
      41             :   !!
      42             :   !!         \b Example
      43             :   !!
      44             :   !!         \code{.f90}
      45             :   !!          out = boxcox(x, lmbda, mask=mask)
      46             :   !!         \endcode
      47             :   !!
      48             :   !!         See also test folder for a detailed example, "pf_tests/test_mo_boxcox".
      49             : 
      50             : 
      51             :   !>        \param[in]  "real(sp/dp) :: x"           Scalar/1D-array with input numbers (`>0.`)
      52             :   !>        \param[in]  "real(sp/dp) :: lmbda"       Exponent power of Box-Cox transform (`>= 0.`)
      53             :   !>        \param[in]  "logical, optional :: mask"  Scalar/1D-array of logical values with `size(x)`.
      54             :   !!                                                 If present, only those locations in vec corresponding
      55             :   !!                                                 to the true values in mask are used.
      56             :   !>        \retval "real(sp/dp) :: boxcox"     Power transformed values (at `mask=.true.`)
      57             : 
      58             :   !>     \note Input values must be positive, i.e. \f$x > 0\f$.
      59             : 
      60             :   !>     \author Matthias Cuntz
      61             :   !>     \date March 2011
      62             :   !!            - Modified Python code of Travis Oliphant (2002): boxcox, llf_boxcox, get_boxcox
      63             :   !!            - Modified numerical recipes: brent, mnbrak, swap, shft
      64             :   !>     \date Dec 2021
      65             :   !!            - Updated doxygen docs
      66             :   INTERFACE boxcox
      67             :     MODULE PROCEDURE boxcox_sp, boxcox_dp
      68             :   END INTERFACE boxcox
      69             : 
      70             :   ! ------------------------------------------------------------------
      71             : 
      72             :   !>        \brief Back-transformation of Box-Cox-transformed data.
      73             : 
      74             :   !>        \details Calculate the inverse Box-Cox given the transformed values and the exponent lambda.
      75             :   !!          \f[ w_\text{Box-Cox}^{-1}(y) =
      76             :   !!                          \begin{cases}
      77             :   !!                           (\lambda y + 1)^{\frac{1}{\lambda}}&\text{, if }\lambda \neq 0 \\
      78             :   !!                           e^{y}&\text{, if }\lambda = 0
      79             :   !!                          \end{cases} \f]
      80             :   !!         If an optional mask is given, then the inverse Box-Cox transformation is only performed on
      81             :   !!         those locations that correspond to true values in the mask.\n
      82             :   !!         \f$y\f$ can be single or double precision. The result will have the same numerical precision.\n
      83             :   !!         \f$y\f$ can be scalar or vector.
      84             :   !!
      85             :   !!         \b Example
      86             :   !!
      87             :   !!         \code{.f90}
      88             :   !!         out = invboxcox(x, lmbda, mask=mask)
      89             :   !!         \endcode
      90             :   !!
      91             :   !!         See also test folder for a detailed example, "pf_tests/test_mo_boxcox".
      92             : 
      93             :   !>        \param[in]  "real(sp/dp) :: y"              Scalar/1D-array with input numbers (`>0.`)
      94             :   !>        \param[in]  "real(sp/dp) :: lmbda"          Exponent power of Box-Cox transform (`>= 0.`)
      95             :   !>        \param[in]  "optional, logical :: mask"     1D-array of logical values with `size(x)`.
      96             :   !!                                                    If present, only those locations in vec corresponding to the
      97             :   !!                                                    true values in mask are used.
      98             :   !!                                                    Only applicable if `x` is a 1D-array.
      99             :   !>        \retval    "real(sp/dp) :: invboxcox"       Back transformed values (at `mask=.true.`)
     100             : 
     101             :   !>     \author Matthias Cuntz
     102             :   !>     \author Juliane Mai
     103             :   !>     \author Sebastian Müller
     104             :   !>     \date March 2011
     105             :   !!            - Modified MC: Python code of Travis Oliphant (2002): boxcox, llf_boxcox, get_boxcox
     106             :   !!            - Modified MC: numerical recipes: brent, mnbrak, swap, shft
     107             :   !!            - Modified JM: scalar version of invboxcox
     108             :   INTERFACE invboxcox
     109             :      MODULE PROCEDURE invboxcox_0d_sp, invboxcox_0d_dp, invboxcox_1d_sp, invboxcox_1d_dp
     110             :   END INTERFACE invboxcox
     111             : 
     112             :   ! ------------------------------------------------------------------
     113             : 
     114             :   PRIVATE
     115             : 
     116             :   ! ------------------------------------------------------------------
     117             : 
     118             : CONTAINS
     119             : 
     120             :   ! ------------------------------------------------------------------
     121             : 
     122           9 :   FUNCTION boxcox_sp(x, lmbda, mask)
     123             : 
     124             :     IMPLICIT NONE
     125             : 
     126             :     REAL(sp), DIMENSION(:), INTENT(in) :: x
     127             :     REAL(sp), INTENT(in) :: lmbda
     128             :     LOGICAL, DIMENSION(:), INTENT(in), OPTIONAL :: mask
     129             :     REAL(sp), DIMENSION(size(x)) :: boxcox_sp
     130             : 
     131           3 :     LOGICAL, DIMENSION(size(x)) :: maske
     132           3 :     REAL(sp) :: lmbda1
     133             : 
     134          63 :     maske(:) = .true.
     135           3 :     if (present(mask)) then
     136           2 :       if (size(mask) /= size(x)) stop 'Error boxcox_sp: size(mask) /= size(x)'
     137          42 :       maske = mask
     138             :     endif
     139          63 :     if (any((le(x, 0.0_sp)) .and. maske)) stop 'Error boxcox_sp: x <= 0'
     140           3 :     if (abs(lmbda) < tiny(0.0_sp)) then
     141          21 :       where (maske)
     142             :         boxcox_sp = log(x)
     143             :       elsewhere
     144             :         boxcox_sp = x
     145             :       end where
     146             :     else
     147           2 :       lmbda1 = 1.0_sp / lmbda
     148          42 :       boxcox_sp = merge((exp(lmbda * log(x)) - 1.0_sp) * lmbda1, x, maske)
     149             :     endif
     150             : 
     151           3 :   END FUNCTION boxcox_sp
     152             : 
     153             : 
     154          12 :   FUNCTION boxcox_dp(x, lmbda, mask)
     155             : 
     156             :     IMPLICIT NONE
     157             : 
     158             :     REAL(dp), DIMENSION(:), INTENT(in) :: x
     159             :     REAL(dp), INTENT(in) :: lmbda
     160             :     LOGICAL, DIMENSION(:), INTENT(in), OPTIONAL :: mask
     161             :     REAL(dp), DIMENSION(size(x)) :: boxcox_dp
     162             : 
     163           3 :     LOGICAL, DIMENSION(size(x)) :: maske
     164           3 :     REAL(dp) :: lmbda1
     165             : 
     166          63 :     maske(:) = .true.
     167           3 :     if (present(mask)) then
     168           2 :       if (size(mask) /= size(x)) stop 'Error boxcox_dp: size(mask) /= size(x)'
     169          42 :       maske = mask
     170             :     endif
     171          63 :     if (any((le(x, 0.0_dp)) .and. maske)) then
     172           0 :       stop 'Error boxcox_dp: x <= 0'
     173             :     end if
     174           3 :     if (abs(lmbda) < tiny(0.0_dp)) then
     175          21 :       where (maske)
     176             :         boxcox_dp = log(x)
     177             :       elsewhere
     178             :         boxcox_dp = x
     179             :       end where
     180             :     else
     181           2 :       lmbda1 = 1.0_dp / lmbda
     182          42 :       boxcox_dp = merge((exp(lmbda * log(x)) - 1.0_dp) * lmbda1, x, maske)
     183             :     endif
     184             : 
     185           3 :   END FUNCTION boxcox_dp
     186             : 
     187             :   ! ------------------------------------------------------------------
     188             : 
     189          12 :   FUNCTION invboxcox_1d_sp(x, lmbda, mask)
     190             : 
     191             :     IMPLICIT NONE
     192             : 
     193             :     REAL(sp), DIMENSION(:), INTENT(in) :: x
     194             :     REAL(sp), INTENT(in) :: lmbda
     195             :     LOGICAL, DIMENSION(:), INTENT(in), OPTIONAL :: mask
     196             :     REAL(sp), DIMENSION(size(x)) :: invboxcox_1d_sp
     197             : 
     198           3 :     LOGICAL, DIMENSION(size(x)) :: maske
     199          63 :     REAL(sp) :: lmbda1
     200          66 :     REAL(sp), DIMENSION(size(x)) :: temp
     201             : 
     202          63 :     maske(:) = .true.
     203           3 :     if (present(mask)) then
     204           2 :       if (size(mask) /= size(x)) stop 'Error invboxcox_1d_sp: size(mask) /= size(x)'
     205          42 :       maske = mask
     206             :     endif
     207           3 :     if (abs(lmbda) < tiny(0.0_sp)) then
     208          21 :       where (maske)
     209             :         invboxcox_1d_sp = exp(x)
     210             :       elsewhere
     211           1 :         invboxcox_1d_sp = x
     212             :       end where
     213             :     else
     214           2 :       lmbda1 = 1.0_sp / lmbda
     215          42 :       temp = lmbda * x + 1.0_sp
     216         122 :       where (temp > 0.0_sp)
     217             :         temp = exp(lmbda1 * log(temp))
     218             :       elsewhere
     219             :         temp = 0.0_sp
     220             :       end where
     221          42 :       invboxcox_1d_sp = merge(temp, x, maske)
     222             :     endif
     223             : 
     224           3 :   END FUNCTION invboxcox_1d_sp
     225             : 
     226          12 :   FUNCTION invboxcox_1d_dp(x, lmbda, mask)
     227             : 
     228             :     IMPLICIT NONE
     229             : 
     230             :     REAL(dp), DIMENSION(:), INTENT(in) :: x
     231             :     REAL(dp), INTENT(in) :: lmbda
     232             :     LOGICAL, DIMENSION(:), INTENT(in), OPTIONAL :: mask
     233             :     REAL(dp), DIMENSION(size(x)) :: invboxcox_1d_dp
     234             : 
     235           3 :     LOGICAL, DIMENSION(size(x)) :: maske
     236          63 :     REAL(dp) :: lmbda1
     237          66 :     REAL(dp), DIMENSION(size(x)) :: temp
     238             : 
     239          63 :     maske(:) = .true.
     240           3 :     if (present(mask)) then
     241           2 :       if (size(mask) /= size(x)) stop 'Error invboxcox_1d_dp: size(mask) /= size(x)'
     242          42 :       maske = mask
     243             :     endif
     244           3 :     if (abs(lmbda) < tiny(0.0_dp)) then
     245          21 :       where (maske)
     246             :         invboxcox_1d_dp = exp(x)
     247             :       elsewhere
     248           1 :         invboxcox_1d_dp = x
     249             :       end where
     250             :     else
     251           2 :       lmbda1 = 1.0_dp / lmbda
     252          42 :       temp = lmbda * x + 1.0_dp
     253         122 :       where (temp > 0.0_dp)
     254             :         temp = exp(lmbda1 * log(temp))
     255             :       elsewhere
     256             :         temp = 0.0_dp
     257             :       end where
     258          42 :       invboxcox_1d_dp = merge(temp, x, maske)
     259             :     endif
     260             : 
     261           3 :   END FUNCTION invboxcox_1d_dp
     262             : 
     263           2 :   FUNCTION invboxcox_0d_sp(x, lmbda)
     264             : 
     265             :     IMPLICIT NONE
     266             : 
     267             :     REAL(sp), INTENT(in) :: x
     268             :     REAL(sp), INTENT(in) :: lmbda
     269             :     REAL(sp) :: invboxcox_0d_sp
     270             : 
     271           2 :     REAL(sp) :: lmbda1
     272           2 :     REAL(sp) :: temp
     273             : 
     274           2 :     if (abs(lmbda) < tiny(0.0_sp)) then
     275           1 :       invboxcox_0d_sp = exp(x)
     276             :     else
     277           1 :       lmbda1 = 1.0_sp / lmbda
     278           1 :       temp = lmbda * x + 1.0_sp
     279           1 :       if (temp > 0.0_sp) then
     280           1 :         temp = exp(lmbda1 * log(temp))
     281             :       else
     282             :         temp = 0.0_sp
     283             :       end if
     284             :       invboxcox_0d_sp = temp
     285             :     endif
     286             : 
     287           3 :   END FUNCTION invboxcox_0d_sp
     288             : 
     289           2 :   FUNCTION invboxcox_0d_dp(x, lmbda)
     290             : 
     291             :     IMPLICIT NONE
     292             : 
     293             :     REAL(dp), INTENT(in) :: x
     294             :     REAL(dp), INTENT(in) :: lmbda
     295             :     REAL(dp) :: invboxcox_0d_dp
     296             : 
     297           2 :     REAL(dp) :: lmbda1
     298           2 :     REAL(dp) :: temp
     299             : 
     300           2 :     if (abs(lmbda) < tiny(0.0_dp)) then
     301           1 :       invboxcox_0d_dp = exp(x)
     302             :     else
     303           1 :       lmbda1 = 1.0_dp / lmbda
     304           1 :       temp = lmbda * x + 1.0_dp
     305           1 :       if (temp > 0.0_dp) then
     306           1 :         temp = exp(lmbda1 * log(temp))
     307             :       else
     308             :         temp = 0.0_dp
     309             :       end if
     310             :       invboxcox_0d_dp = temp
     311             :     endif
     312             : 
     313           2 :   END FUNCTION invboxcox_0d_dp
     314             : 
     315             : END MODULE mo_boxcox

Generated by: LCOV version 1.16