LCOV - code coverage report
Current view: top level - src - mo_smi.F90 (source / functions) Hit Total Coverage
Test: SMI coverage Lines: 111 117 94.9 %
Date: 2024-04-30 09:16:25 Functions: 5 5 100.0 %

          Line data    Source code
       1             : !> \file mo_smi.f90
       2             : !> \brief   \copybrief mo_smi
       3             : !> \details \copydetails mo_smi
       4             : 
       5             : !> \brief   SOIL MOISTURE INDEX
       6             : !> \details Estimate SMI based on monthly values of SM_mHM
       7             : !> \author  Luis Samaniego
       8             : !> \date    Feb 2011
       9             : !> \author  Stephan Thober
      10             : !> \date    16.12.2014
      11             : !!          - added evalSMI capability
      12             : !> \author  Stephan Thober
      13             : !> \date    07.11.2017
      14             : !!          - added inversion of CDFs
      15             : !> \author  Stephan Thober
      16             : !> \date    02.08.2019
      17             : !!          - added non-full year
      18             : !> \copyright Copyright 2005-\today, the CHS Developers, Luis Samaniego, Sabine Attinger: All rights reserved.
      19             : !! SMI is released under the LGPLv3+ license \license_note
      20             : module mo_smi
      21             :   !
      22             :   use mo_smi_global_variables, only: period
      23             :   use mo_kind,          only: dp
      24             :   !
      25             :   implicit none
      26             :   ! public routines
      27             :   public :: optimize_width
      28             :   public :: calSMI
      29             :   public :: invSMI
      30             :   !
      31             : 
      32             :   private
      33             : 
      34             : 
      35             : contains
      36             : 
      37             :   !> \brief subroutine for estimating SMI for first array
      38           1 :   subroutine optimize_width( opt_h, silverman_h, SM, nCalendarStepsYear, per_kde)
      39             :     !$ use omp_lib
      40             :     use mo_kind,          only : i4, sp
      41             :     use mo_kernel,        only : kernel_density_h
      42             :     implicit none
      43             : 
      44             :     ! input variables
      45             :     logical,                  intent(in)    :: silverman_h ! optimize kernel width
      46             :     real(sp), dimension(:,:), intent(in)    :: SM
      47             :     integer(i4),              intent(in)    :: nCalendarStepsYear
      48             :     type(period),             intent(in)    :: per_kde
      49             : 
      50             :     ! output variables
      51             :     real(dp), dimension(:,:), intent(inout) :: opt_h
      52             : 
      53             :     ! local variables
      54             :     integer(i4)                             :: ii     ! cell index
      55             :     integer(i4)                             :: mm     ! month index
      56           1 :     real(dp), dimension(:),     allocatable :: X
      57           1 :     logical,                    allocatable :: t_mask_kde(:)
      58           1 :     integer(i4),                allocatable :: time_kde(:)
      59             : 
      60             : 
      61             :     call get_time_indizes(time_kde, per_kde, nCalendarStepsYear)
      62             : 
      63             :     !$OMP parallel default(shared) &
      64             :     !$OMP private(ii,mm,X, t_mask_kde)
      65             :     !$OMP do SCHEDULE(STATIC)
      66          13 :     do mm = 1, nCalendarStepsYear
      67             : 
      68        4344 :       t_mask_kde = (time_kde .eq. mm)
      69             : 
      70         277 :       do ii = 1, size( SM, 1 )
      71             : #ifdef SMIDEBUG
      72             :          if ((mod(ii, 100) .eq. 0_i4) .and. (mm .eq. nCalendarStepsYear)) print *, ii, mm
      73             : #endif
      74             :          !
      75       95832 :          allocate(X(count(t_mask_kde)))
      76        8184 :          X(:) = pack(SM( ii, :), t_mask_kde)
      77             :          !
      78             :          ! determine kernel width if these have not been read from file
      79         264 :          opt_h(ii,mm) = kernel_density_h( X(:), silverman = silverman_h )
      80         276 :          deallocate(X)
      81             :        end do
      82             :     end do
      83             :     !$OMP end do
      84             :     !$OMP end parallel
      85           1 :   end subroutine optimize_width
      86             : 
      87             :   !!======================================================
      88             :   !! ESTIMATE SMI
      89             :   !!======================================================
      90             :   !> \brief subroutine for calculating SMI for second array with pdf of first one
      91           4 :   subroutine calSMI( hh, sm_kde, sm_eval, nCalendarStepsYear, SMI, per_kde, per_eval)
      92             : 
      93             :     use mo_kind,          only: i4, sp, dp
      94             :     use mo_kernel,        only: kernel_cumdensity
      95             :     use mo_constants,     only: nodata_dp, nodata_sp
      96             : 
      97             :     implicit none
      98             : 
      99             :     ! input variables
     100             :     real(dp), dimension(:,:), intent(in)  :: hh
     101             :     real(sp), dimension(:,:), intent(in)  :: sm_kde
     102             :     real(sp), dimension(:,:), intent(in)  :: sm_eval
     103             :     integer(i4),              intent(in)  :: nCalendarStepsYear
     104             :     type(period),             intent(in)  :: per_kde
     105             :     type(period),             intent(in)  :: per_eval
     106             : 
     107             :     ! output variables
     108             :     real(sp), dimension(:,:), intent(out) :: SMI
     109             : 
     110             :     ! local variables
     111             :     integer(i4)                           :: mm  ! loop index
     112             :     integer(i4)                           :: ii  ! cell index
     113             :     real(dp), dimension(:), allocatable   :: cdf
     114             :     real(dp), dimension(:), allocatable   :: X_kde
     115             :     real(dp), dimension(:), allocatable   :: X_eval
     116             :     integer(i4)                           :: n_time
     117           4 :     logical,                allocatable   :: t_mask_kde(:)
     118           4 :     integer(i4),            allocatable   :: time_kde(:)
     119           4 :     logical,                allocatable   :: t_mask_eval(:)
     120           4 :     integer(i4),            allocatable   :: time_eval(:)
     121             :     real(sp), dimension(:), allocatable   :: dummy_1d_sp
     122             : 
     123             :     call get_time_indizes(time_kde, per_kde, nCalendarStepsYear)
     124             :     call get_time_indizes(time_eval, per_eval, nCalendarStepsYear)
     125             : #ifdef SMIDEBUG
     126             :     print *, 'First 10 values of time_kde:'
     127             :     print *, time_kde(:10)
     128             :     print *, 'First 10 values of time_eval:'
     129             :     print *, time_eval(:10)
     130             : #endif
     131             : 
     132         405 :     do mm = 1,  nCalendarStepsYear   ! time loop
     133             : 
     134    12545307 :       t_mask_kde = (time_kde .eq. mm)
     135      119002 :       t_mask_eval = (time_eval .eq. mm)
     136      118601 :       n_time = count(t_mask_eval)
     137             : #ifdef SMIDEBUG
     138             :       print *, 'Processing: timestep, number of eval timesteps, number of kde timesteps'
     139             :       print *, mm, n_time, count(t_mask_kde)
     140             : #endif
     141             : 
     142             :       ! check whether there is data for that timestep to be calculated
     143         401 :       if (n_time .eq. 0_i4) cycle
     144             : 
     145         405 :       call cellSMI(SM_kde, t_mask_kde, SM_eval, t_mask_eval, hh(:, mm), SMI)
     146             :     end do
     147             : 
     148             :     ! do leap days
     149           4 :     if (per_eval%n_leap_days .gt. 0) then
     150             : 
     151           0 :       mm = 60 ! take cdf of March first
     152           0 :       t_mask_kde = (time_kde .eq. mm)
     153           0 :       t_mask_eval = (time_eval .eq. -1_i4)
     154           0 :       n_time = count(t_mask_eval)
     155             : 
     156           0 :       call cellSMI(SM_kde, t_mask_kde, SM_eval, t_mask_eval, hh(:, mm), SMI)
     157             :     end if
     158             : 
     159           4 :   end subroutine calSMI
     160             : 
     161         329 :   subroutine cellSMI(SM_kde, t_mask_kde, SM_eval, t_mask_eval, hh, SMI)
     162             : 
     163             :     use mo_kind,      only: i4, sp
     164             :     use mo_kernel,    only: kernel_cumdensity
     165             :     use mo_constants, only: nodata_sp
     166             : 
     167             :     implicit none
     168             : 
     169             :     real(sp), intent(in) :: SM_kde(:, :), SM_eval(:, :)
     170             :     logical,  intent(in) :: t_mask_kde(:), t_mask_eval(:)
     171             :     real(dp), intent(in) :: hh(:)
     172             :     real(sp), intent(inout) :: SMI(:, :)
     173             : 
     174             :     integer(i4)                           :: ii  ! cell index
     175         329 :     real(dp), dimension(:), allocatable   :: cdf
     176         329 :     real(dp), dimension(:), allocatable   :: X_kde
     177             :     real(dp), dimension(:), allocatable   :: X_eval
     178         329 :     real(sp), dimension(:), allocatable   :: dummy_1d_sp
     179             : 
     180             : 
     181             :     !$OMP parallel default(shared) &
     182             :     !$OMP private(ii, X_kde, X_eval, cdf, dummy_1d_sp)
     183    10311327 :     allocate ( X_kde       (count(t_mask_kde)))
     184       99652 :     allocate ( X_eval      (count(t_mask_eval)))
     185       99652 :     allocate ( cdf         (count(t_mask_eval)))
     186         987 :     allocate ( dummy_1d_sp (size(t_mask_eval, dim=1)))
     187             : 
     188             :     !$OMP do SCHEDULE(STATIC)
     189        4567 :     do ii = 1, size(SM_kde,1)          ! cell loop
     190             : 
     191             : #ifdef SMIDEBUG
     192             :       if ((mod(ii, 10000) .eq. 0_i4)) print *, ii
     193             : #endif
     194             : 
     195   123832718 :       X_kde(:)  = pack(real( SM_kde(ii,:), dp), t_mask_kde)
     196     1274868 :       X_eval(:) = pack(real(SM_eval(ii,:), dp), t_mask_eval)
     197             : 
     198       23788 :       cdf(:) = kernel_cumdensity(x_kde, hh(ii), xout=x_eval)
     199             : 
     200       23788 :       dummy_1d_sp = unpack(real(cdf(:), sp), t_mask_eval, nodata_sp)
     201     1275197 :       SMI(ii, :) = merge(dummy_1d_sp, SMI(ii, :), t_mask_eval)
     202             : 
     203             :     end do
     204             :     !$OMP end do
     205             :     !$OMP end parallel
     206             : 
     207             :     ! arrays are deallocated if openmp is active
     208         329 :     if (allocated(x_kde))       deallocate( x_kde)
     209         329 :     if (allocated(x_eval))      deallocate( X_eval)
     210         329 :     if (allocated(cdf))         deallocate( cdf)
     211         329 :     if (allocated(dummy_1d_sp)) deallocate( dummy_1d_sp )
     212             : 
     213         329 :   end subroutine cellSMI
     214             : 
     215             :   !> \brief create objective function of kernel_cumdensity and minimize it using
     216             :   !!        nelmin because function is monotone
     217           1 :   subroutine invSMI(sm_kde, hh, SMI_invert, nCalendarStepsYear, per_kde, per_smi, SM_invert)
     218             : 
     219             :     use mo_kind,          only: i4, sp, dp
     220             :     use mo_message,       only: message
     221             :     use mo_constants,     only: nodata_sp, nodata_dp
     222             :     use mo_kernel,        only: kernel_cumdensity
     223             : 
     224             :     implicit none
     225             : 
     226             :     ! input variables
     227             :     real(dp), dimension(:,:),              intent(in)  :: hh
     228             :     real(sp), dimension(:,:),              intent(in)  :: sm_kde
     229             :     real(sp), dimension(:,:),              intent(in)  :: SMI_invert
     230             :     integer(i4),                           intent(in)  :: nCalendarStepsYear
     231             :     type(period),                          intent(in)  :: per_kde
     232             :     type(period),                          intent(in)  :: per_smi
     233             : 
     234             :     ! output variables
     235             :     real(sp), dimension(:,:), allocatable, intent(out) :: SM_invert
     236             : 
     237             :     ! local variables
     238           1 :     logical,                allocatable :: t_mask_kde(:), t_mask_smi(:)
     239           1 :     integer(i4),            allocatable :: time_kde(:), time_smi(:)
     240             :     integer(i4)                         :: n_cells
     241             :     integer(i4)                         :: n_years_kde, n_years_invert
     242             :     integer(i4)                         :: ii, yy, mm ! loop variables
     243             :     integer(i4)                         :: xx_n_sample
     244             :     integer(i4), dimension(1)           :: idx_invert
     245           1 :     real(sp), dimension(:), allocatable :: dummy_1d_sp
     246             :     real(dp)                            :: hh_kde
     247             :     real(dp)                            :: xx_min, xx_max, xx_h
     248           1 :     real(dp), dimension(:), allocatable :: y_inv, x_inv
     249           1 :     real(dp), dimension(:), allocatable :: xx_cdf, yy_cdf
     250           1 :     real(dp), dimension(:), allocatable :: xx_kde
     251             : 
     252             :     ! initialize extents
     253           1 :     n_cells        = size(SMI_invert, 1)
     254           1 :     xx_n_sample    = 2000_i4 ! gives precision of at least 0.0005 in SM
     255           1 :     allocate(xx_cdf(xx_n_sample))
     256           1 :     allocate(yy_cdf(xx_n_sample))
     257        2001 :     xx_cdf = nodata_dp
     258        2001 :     yy_cdf = nodata_dp
     259             : 
     260             :     ! initialize output array
     261           4 :     allocate(SM_invert(n_cells, size(SMI_invert, 2)))
     262         116 :     SM_invert = nodata_sp
     263             : 
     264             :     ! get time indizes
     265             :     call get_time_indizes(time_kde, per_kde, nCalendarStepsYear)
     266             :     call get_time_indizes(time_smi, per_smi, nCalendarStepsYear)
     267             : 
     268           1 :     call message('')
     269           1 :     call message('  start inversion of CDF')
     270          13 :     do mm = 1, nCalendarStepsYear
     271             : 
     272        4344 :       t_mask_kde = (time_kde .eq. mm)
     273          84 :       t_mask_smi = (time_smi .eq. mm)
     274          72 :       if (count(t_mask_smi) .eq. 0_i4) cycle
     275             : 
     276             :       !$OMP parallel default(shared) &
     277             :       !$OMP private(yy, xx_kde, hh_kde, y_inv, xx_min, xx_max, xx_h, xx_cdf, yy_cdf, idx_invert, x_inv, dummy_1d_sp)
     278        1815 :       allocate(xx_kde(count(t_mask_kde)))
     279          40 :       allocate(y_inv(count(t_mask_smi)))
     280          40 :       allocate(x_inv(count(t_mask_smi)))
     281          40 :       allocate(dummy_1d_sp(count(t_mask_smi)))
     282             : 
     283             :       !$OMP do SCHEDULE(STATIC)
     284         115 :       do ii = 1, n_cells
     285         110 :         if (modulo(ii, 1000) .eq. 0) print *, ii, n_cells
     286       39710 :         xx_kde(:) = pack(real(SM_kde( ii, : ), dp), t_mask_kde)
     287         110 :         hh_kde    = hh(ii, mm)
     288         660 :         y_inv(:)  = pack(real(SMI_invert(ii, :), dp), t_mask_smi)
     289             : 
     290             :         ! sample cdf
     291        3520 :         xx_min    = max(0._dp, minval(xx_kde - 10._dp * hh_kde))
     292        3520 :         xx_max    = min(1._dp, maxval(xx_kde + 10._dp * hh_kde))
     293         110 :         xx_h      = (xx_max - xx_min) / real(xx_n_sample, dp)
     294      220110 :         do yy = 1, xx_n_sample
     295      220110 :            xx_cdf(yy) = xx_min + (yy - 1_i4) * xx_h
     296             :         end do
     297      220110 :         xx_cdf = merge(1._dp, xx_cdf, xx_cdf .gt. 1._dp)
     298      220220 :         yy_cdf = kernel_cumdensity(xx_kde, hh_kde, xout=xx_cdf)
     299             : 
     300         220 :         do yy = 1, size(y_inv, 1)
     301      220220 :           idx_invert = minloc(abs(y_inv(yy) - yy_cdf))
     302         220 :           x_inv(yy) = xx_cdf(idx_invert(1))
     303             :         end do
     304             : 
     305         220 :         dummy_1d_sp = unpack(real(x_inv(:), sp), t_mask_smi, nodata_sp)
     306         665 :         SM_invert(ii, :) = merge(dummy_1d_sp, SM_invert(ii, :), t_mask_smi)
     307             : 
     308             :       end do
     309             :       !$OMP end do
     310             :       !$OMP end parallel
     311             : 
     312           5 :       if (allocated(xx_kde))      deallocate(xx_kde)
     313           5 :       if (allocated(y_inv))       deallocate(y_inv)
     314           5 :       if (allocated(x_inv))       deallocate(x_inv)
     315           6 :       if (allocated(dummy_1d_sp)) deallocate(dummy_1d_sp)
     316             : 
     317             :     end do
     318           1 :     call message('  finish inversion of CDF... ok')
     319           1 :     call message('')
     320             : 
     321             :     ! free memory
     322           1 :     deallocate(xx_cdf, yy_cdf)
     323             : 
     324           2 :   end subroutine invSMI
     325             : 
     326             : 
     327          11 :   subroutine get_time_indizes(time, per, nCalendarStepsYear)
     328             : 
     329             :     use mo_kind,             only: i4, dp
     330             :     use mo_smi_global_variables, only: period
     331             :     use mo_julian,           only: dec2date, date2dec
     332             : 
     333             :     implicit none
     334             : 
     335             :     integer(i4),              intent(in)  :: nCalendarStepsYear
     336             :     type(period),             intent(in)  :: per
     337             :     integer(i4), allocatable, intent(out) :: time(:)
     338             :     integer(i4) :: ii, jj, start, dd, mm
     339             : 
     340             :     ! remove leap days
     341          33 :     allocate(time(size(per%time_points, dim=1)))
     342       37174 :     time(:) = 0_i4
     343             : 
     344          11 :     if (nCalendarStepsYear .eq. 12_i4) then
     345           9 :       start = per%m_start
     346             :     else
     347           2 :       start = int(per%j_start - date2dec(31, 12, per%y_start - 1), i4)
     348             :       ! account for removed leap days
     349           2 :       if ((int((date2dec(31, 12, per%y_start) - date2dec(1, 1, per%y_start) + 1), i4) .eq. 366) .and. per%m_start .gt. 2) then
     350           0 :         start = start - 1
     351             :       end if
     352             :     end if
     353             : 
     354             : 
     355          11 :     jj = start
     356       37174 :     do ii = 1, size(time)
     357       37163 :       time(ii) = jj
     358       37163 :       call dec2date(real(per%j_start + per%time_points(ii) - 1, dp), dd=dd, mm=mm)
     359       37163 :       if ((dd .eq. 29) .and. (mm .eq. 2) .and. (nCalendarStepsYear .ne. 12)) then
     360          23 :         time(ii) = -1
     361          23 :         cycle
     362             :       end if
     363       37140 :       jj = jj + 1_i4
     364       37151 :       if (jj .gt. nCalendarStepsYear) jj = 1_i4
     365             :     end do
     366             : 
     367          11 :   end subroutine get_time_indizes
     368             : 
     369             : end module mo_smi

Generated by: LCOV version 1.16