0.6.2-dev0
FORCES
FORtran lib for Comp. Env. Sys.
Loading...
Searching...
No Matches
mo_eckhardt_filter.f90
Go to the documentation of this file.
1!> \file mo_eckhardt_filter.f90
2!> \brief \copybrief mo_eckhardt_filter
3!> \details \copydetails mo_eckhardt_filter
4
5!> \brief Eckhardt filter for baseflow index calculation.
6!> \details This module provides routines for the Eckardt filter to analyse discharge time series and extract the baseflow.
7!! The filter was proposed in Eckhardt (2008, doi: 10.1016/j.jhydrol.2008.01.005)
8!> \version 0.1
9!> \authors Sebastian Mueller
10!> \authors Mariaines Di Dato
11!> \date Apr 2022
12!> \copyright Copyright 2005-\today, the CHS Developers, Sabine Attinger: All rights reserved.
13!! FORCES is released under the LGPLv3+ license \license_note
15
16 use mo_kind, only: i4, dp
17 use mo_moment, only: mean
18 use mo_percentile, only: percentile
19 use mo_append, only: append
20 use mo_nelmin, only: nelminrange
21 use mo_message, only: error_message
22
23 implicit none
24 private
25 public :: fit_alpha
26 public :: eckhardt_filter_fit
27 public :: eckhardt_filter
28 public :: weekly_average
29 public :: bfi
30
31 real(dp), allocatable :: temp_d7(:)
32 logical, allocatable :: temp_qmin_mask(:), temp_mask(:)
33
34contains
35
36 !> \brief Eckhardt filter for baseflow calculation from discharge time series with fitting.
37 !> \details This filter was proposed in Eckhardt (2008, doi: 10.1016/j.jhydrol.2008.01.005)
38 !> \return baseflow
39 function eckhardt_filter_fit(discharge, mask) result(baseflow)
40
41 !> array with daily discharge
42 real(dp), intent(in) :: discharge(:)
43 !> mask for daily discharge
44 logical, intent(in), optional :: mask(:)
45
46 real(dp), allocatable :: baseflow(:)
47
48 real(dp) :: alpha
49
50 alpha = fit_alpha(discharge, mask=mask)
51 baseflow = eckhardt_filter(alpha, discharge, mask=mask)
52
53 end function eckhardt_filter_fit
54
55 !> \brief Fitted alpha parameter for the Eckhardt filter.
56 !> \details This filter was proposed in Eckhardt (2008, doi: 10.1016/j.jhydrol.2008.01.005)
57 !> \return alpha parameter for eckard filter
58 real(dp) function fit_alpha(discharge, mask)
59
60 !> array with daily discharge
61 real(dp), intent(in) :: discharge(:)
62 !> mask for daily discharge
63 logical, intent(in), optional :: mask(:)
64
65 real(dp) :: alpha_out(1)
66 real(dp), allocatable :: q_min(:), dummy(:)
67 logical, dimension(size(discharge)) :: mask_
68 integer(i4) :: i, j
69
70 mask_(:) = .true.
71 if ( present(mask) ) mask_ = mask
72
73 temp_d7 = weekly_average(discharge, mask=mask)
74 allocate(q_min(0))
75 do i = 1, size(temp_d7), 365
76 j = min(i+364, size(temp_d7))
77 ! only use values in mask
78 ! TODO: do we need a threshold for number in mask here?
79 if ( any(mask_(i : j)) ) call append(q_min, minval(temp_d7(i : j), mask=mask_(i : j)))
80 end do
81 if ( size(q_min) < 2 ) call error_message("Eckhardt filter: Less than 2 years of discharge observations! (min. 10 recommended)")
82
83 allocate(temp_qmin_mask(size(discharge)))
84 allocate(temp_mask(size(discharge)))
85 temp_mask = mask_
86 temp_qmin_mask = (temp_d7 < percentile(q_min, 10.0_dp, mode_in=4))
87 temp_qmin_mask = temp_qmin_mask .and. temp_mask
88
89 ! set values outside of mask to 1 in d7
90 allocate(dummy(count(.not.mask_)))
91 dummy(:) = 1.0_dp ! [1.0_dp, i=1,count(.not.mask_)]
92 temp_d7 = unpack( &
93 vector=dummy, &
94 mask=.not.mask_, &
95 field=temp_d7 &
96 )
97 deallocate(dummy)
98
99 alpha_out = nelminrange( &
100 func=func, &
101 pstart=[0.9_dp], &
102 prange=reshape([0._dp, 1._dp], [1, 2]) &
103 )
104 fit_alpha = alpha_out(1)
105
106 deallocate(temp_qmin_mask)
107 deallocate(temp_mask)
108 deallocate(temp_d7)
109
110 end function fit_alpha
111
112 !> \brief Eckhardt filter for baseflow calculation from discharge time series.
113 !> \details This filter was proposed in Eckhardt (2008, doi: 10.1016/j.jhydrol.2008.01.005)
114 !> \return baseflow
115 function eckhardt_filter(alpha, discharge, mask) result(baseflow)
116
117 !> filter parameter
118 real(dp), intent(in) :: alpha
119 !> array with daily discharge
120 real(dp), intent(in) :: discharge(:)
121 !> mask for daily discharge
122 logical, intent(in), optional :: mask(:)
123
124 real(dp), allocatable :: baseflow(:)
125
126 real(dp), allocatable :: d7(:), d7_perc(:), d_temp(:), b_temp(:)
127 logical, dimension(size(discharge)) :: mask_
128 real(dp) :: bfi_max
129 integer(i4) :: i
130
131 mask_(:) = .true.
132 if ( present(mask) ) mask_ = mask
133
134 allocate(baseflow(size(discharge)))
135 allocate(d7_perc(size(discharge)))
136
137 ! 20 percent percentile with Linear interpolation
138 d7 = weekly_average(discharge, mask=mask)
139 d7_perc(:) = percentile(d7, 20.0_dp, mask=mask, mode_in=4)
140 bfi_max = bfi(d7_perc, discharge, mask=mask)
141
142 allocate(b_temp(count(mask_)))
143 allocate(d_temp(count(mask_)))
144 d_temp = pack(discharge, mask=mask_)
145
146 ! Applying the equation Eq. (6) from Eckhardt (2008) (only at mask)
147 b_temp(1) = ((1 - alpha)*bfi_max * d_temp(1)) / (1 - alpha*bfi_max)
148 do i = 2, size(d_temp)
149 b_temp(i) = ((1 - bfi_max)*alpha*b_temp(i-1) + (1 - alpha)*bfi_max*d_temp(i)) / (1 - alpha*bfi_max)
150 end do
151 baseflow(:) = 0.0_dp
152 baseflow = unpack(vector=b_temp, mask=mask_, field=baseflow)
153
154 end function eckhardt_filter
155
156 !> \brief This function returns the 7days-averaged discharge.
157 !> \return array with weekly moving average
158 function weekly_average(discharge, mask) result(d7)
159
160 !> array with daily discharge
161 real(dp), intent(in) :: discharge(:)
162 !> mask for daily discharge
163 logical, intent(in), optional :: mask(:)
164
165 real(dp), allocatable :: d7(:)
166
167 logical, dimension(size(discharge)) :: mask_
168 integer(i4) :: i, n, m
169
170 mask_(:) = .true.
171 if ( present(mask) ) mask_ = mask
172
173 allocate(d7(size(discharge)))
174 d7(:) = 0.0_dp
175
176 do i = 1, size(discharge)
177 n = max(1,i-3)
178 m = min(size(discharge),i+3)
179 ! TODO: do we need a threshold for number in mask here?
180 if ( any(mask_(n : m)) ) d7(i) = mean(discharge(n : m), mask=mask_(n : m))
181 end do
182
183 end function weekly_average
184
185 !> \brief Calculate the baseflow index as ratio between baseflow and discharge.
186 !> \return baseflow index
187 real(dp) function bfi(baseflow, discharge, mask)
188
189 !> array with daily baseflow values
190 real(dp), intent(in) :: baseflow(:)
191 !> array with daily discharge
192 real(dp), intent(in) :: discharge(:)
193 !> mask for daily discharge
194 logical, intent(in), optional :: mask(:)
195
196 logical, dimension(size(discharge)) :: mask_
197
198 mask_(:) = .true.
199 if ( present(mask) ) mask_ = mask
200
201 bfi = sum(baseflow, mask=mask_) / sum(discharge, mask=mask_)
202
203 end function bfi
204
205 !> \brief Target function for fitting Eckhardt filter.
206 !> \return Objective value.
207 function func(pp)
208
209 real(dp), dimension(:), intent(in) :: pp !< alpha (single value)
210
211 real(dp) :: func
212
213 real(dp), allocatable :: baseflow(:)
214
215 baseflow = eckhardt_filter(alpha=pp(1), discharge=temp_d7, mask=temp_mask)
216 func = mean((baseflow/temp_d7 - 1)**2, mask=temp_qmin_mask)
217
218 end function func
219
220end module mo_eckhardt_filter
Append (rows) scalars, vectors, and matrixes onto existing array.
Mean of a vector.
Minimizes a user-specified function using the Nelder-Mead algorithm.
Append values on existing arrays.
Definition mo_append.f90:20
Eckhardt filter for baseflow index calculation.
real(dp) function, dimension(:), allocatable, public eckhardt_filter_fit(discharge, mask)
Eckhardt filter for baseflow calculation from discharge time series with fitting.
real(dp) function, public fit_alpha(discharge, mask)
Fitted alpha parameter for the Eckhardt filter.
real(dp) function, dimension(:), allocatable, public weekly_average(discharge, mask)
This function returns the 7days-averaged discharge.
real(dp) function, public bfi(baseflow, discharge, mask)
Calculate the baseflow index as ratio between baseflow and discharge.
real(dp) function func(pp)
Target function for fitting Eckhardt filter.
real(dp) function, dimension(:), allocatable, public eckhardt_filter(alpha, discharge, mask)
Eckhardt filter for baseflow calculation from discharge time series.
Define number representations.
Definition mo_kind.F90:17
integer, parameter i4
4 Byte Integer Kind
Definition mo_kind.F90:40
integer, parameter dp
Double Precision Real Kind.
Definition mo_kind.F90:46
Write out concatenated strings.
subroutine, public error_message(t01, t02, t03, t04, t05, t06, t07, t08, t09, t10, t11, t12, t13, t14, t15, t16, uni, advance, show, raise, reset_format)
Write out an error message to stderr and call stop 1.
Statistical moments.
Definition mo_moment.f90:25
Nelder-Mead algorithm.
Definition mo_nelmin.f90:14
Median and percentiles.