1 ! This module defines and instantiates objects
2 ! for a hybrid persistence levelpool type
3 ! reservoir. The hybrid reservoir type
4 ! inherits input and output types from the
5 ! reservoir base module and calls instantiation
6 ! of these into sub-objects. The hybrid
7 ! reservoir type also points to types for
8 ! hybrid properties and state and calls
9 ! instantiation of these into sub-objects.
10 ! A pointer to a levelpool reservoir object
11 ! is also held in state, and this module
12 ! instantiates that levelpool object. There
13 ! is also a subroutine to run hybrid reservoir
14 ! that is derived from the reservoir base
15 ! type interface to run reservoir.
17 module module_persistence_levelpool_hybrid
19 use module_persistence_levelpool_hybrid_properties, only: hybrid_properties_interface
20 use module_persistence_levelpool_hybrid_state, only: hybrid_state_interface
21 use module_levelpool, only: levelpool
22 use module_reservoir_utilities, only: modify_for_projected_storage, warn_negative_inflow, &
23 create_hybrid_diagnostic_log_file, &
24 log_hybrid_diagnostic_data
25 use module_reservoir, only: reservoir, reservoir_input, reservoir_output
26 use module_reservoir_read_timeslice_data, only: usgs_timeslice_data, usace_timeslice_data, timeslice_data_type
27 use module_hydro_stop, only: HYDRO_stop
28 use iso_fortran_env, only: int64
31 ! Extend/derive hybrid type from the abstract base
32 ! type for reservoirs.
33 type, extends(reservoir) :: persistence_levelpool_hybrid
35 ! Define pointers to sub-types / sub-objects to and
36 ! held by a level pool reservoir object.
37 type (hybrid_properties_interface), pointer :: properties => null()
38 type (hybrid_state_interface), pointer :: state => null()
39 type (timeslice_data_type), pointer :: timeslice_data => null()
41 logical :: pointer_allocation_guard = .false.
45 procedure :: init => hybrid_init
46 procedure :: destroy => hybrid_destroy
47 procedure :: run => run_hybrid_reservoir
49 end type persistence_levelpool_hybrid
52 integer, parameter :: log_warning = 6
54 integer, parameter :: log_warning = 78
60 subroutine hybrid_init(this, water_elevation, &
61 lake_area, weir_elevation, weir_coeffecient, &
62 weir_length, dam_length, orifice_elevation, orifice_coefficient, &
63 orifice_area, lake_max_water_elevation, initial_fractional_depth, &
64 lake_number, reservoir_type, reservoir_parameter_file, start_date, &
65 usgs_timeslice_path, usace_timeslice_path, observation_lookback_hours, &
66 observation_update_time_interval_seconds)
68 class(persistence_levelpool_hybrid), intent(inout) :: this ! object being initialized
69 real, intent(inout) :: water_elevation ! meters AMSL
70 real, intent(in) :: lake_area ! area of lake (km^2)
71 real, intent(in) :: weir_elevation ! bottom of weir elevation (meters AMSL)
72 real, intent(in) :: weir_coeffecient ! weir coefficient
73 real, intent(in) :: weir_length ! weir length (meters)
74 real, intent(in) :: dam_length ! dam length (meters)
75 real, intent(in) :: orifice_elevation ! orifice elevation (meters AMSL)
76 real, intent(in) :: orifice_coefficient ! orifice coefficient
77 real, intent(in) :: orifice_area ! orifice area (meters^2)
78 real, intent(in) :: lake_max_water_elevation ! max water elevation (meters)
79 real, intent(in) :: initial_fractional_depth ! initial fraction water depth
80 integer(kind=int64), intent(in) :: lake_number ! lake number
81 integer, intent(in) :: reservoir_type ! reservoir type
82 character(len=*), intent(in) :: reservoir_parameter_file
83 character(len=19), intent(in) :: start_date
84 character(len=256), intent(in) :: usgs_timeslice_path
85 character(len=256), intent(in) :: usace_timeslice_path
86 integer, intent(in) :: observation_lookback_hours
87 integer, intent(in) :: observation_update_time_interval_seconds
88 character(len=15) :: lake_number_string
89 character(len=15) :: reservoir_type_string
93 ! Create diagnostic log file only for development/debugging purposes
94 call create_hybrid_diagnostic_log_file(lake_number)
97 if (this%pointer_allocation_guard .eqv. .false. ) then
99 ! Call to initialize timeslice data object. This object is a singleton for either
100 ! USGS (U.S. Geological Survey) or USACE (U.S. Army Corps of Engineers) data source,
101 ! so if another reservoir has already initialized the object of a particular data
102 ! source, it will return immediately.
103 if (reservoir_type == 2) then
104 call usgs_timeslice_data%init(start_date, usgs_timeslice_path, &
105 reservoir_parameter_file, "usgs", observation_lookback_hours)
107 ! Assign pointer to usgs_timeslice_data object
108 this%timeslice_data=>usgs_timeslice_data
110 else if (reservoir_type == 3) then
111 call usace_timeslice_data%init(start_date, usace_timeslice_path, &
112 reservoir_parameter_file, "usace", observation_lookback_hours)
114 ! Assign pointer to usace_timeslice_data object
115 this%timeslice_data=>usace_timeslice_data
118 ! Call hydro_stop if reservoir_type is not USGS or USACE because the
119 ! data source is not available.
120 write(lake_number_string, "(I15)") lake_number
121 write(reservoir_type_string, "(I15)") reservoir_type
122 call hydro_stop("ERROR: Incorrect reservoir type for reservoir " // trim(ADJUSTL(lake_number_string)) // &
123 ". Expected reservoir type 2 or 3 and instead received reservoir type " &
124 // trim(ADJUSTL(reservoir_type_string)) // "." )
127 ! try to allocate input
128 allocate ( this%input )
129 if ( .not. associated(this%input) ) then
130 ! if the input structure could not be created, call hydro_stop.
131 write(lake_number_string, "(I15)") lake_number
132 call hydro_stop("ERROR: Failure to allocate hybrid input structure for reservoir " &
133 // trim(ADJUSTL(lake_number_string)) // ".")
135 ! initialize the input structure
136 call this%input%init()
139 ! try to allocate output
140 allocate ( this%output )
141 if ( .not. associated(this%output) ) then
142 ! if the output structure could not be created, call hydro_stop.
143 write(lake_number_string, "(I15)") lake_number
144 call hydro_stop("ERROR: Failure to allocate hybrid output structure for reservoir " &
145 // trim(ADJUSTL(lake_number_string)) // ".")
147 ! initialize the output structure
148 call this%output%init()
151 ! try to allocate properties
152 allocate ( this%properties )
153 if ( .not. associated(this%properties) ) then
154 ! if the properties structure could not be created, call hydro_stop.
155 write(lake_number_string, "(I15)") lake_number
156 call hydro_stop("ERROR: Failure to allocate hybrid properties structure for reservoir " &
157 // trim(ADJUSTL(lake_number_string)) // ".")
159 ! initialize hybrid properties
160 call this%properties%init(lake_area, lake_max_water_elevation, orifice_elevation, lake_number, &
161 reservoir_type, observation_lookback_hours, observation_update_time_interval_seconds, &
162 reservoir_parameter_file)
164 this%pointer_allocation_guard = .true.
166 ! try to allocate state
167 allocate ( this%state )
168 if ( .not. associated(this%state) ) then
169 ! if the state structure could not be created, call hydro_stop.
170 write(lake_number_string, "(I15)") lake_number
171 call hydro_stop("ERROR: Failure to allocate hybrid state structure for reservoir " &
172 // trim(ADJUSTL(lake_number_string)) // ".")
174 ! initialize hybrid state
175 call this%state%init(water_elevation, lake_area, lake_max_water_elevation, orifice_elevation, &
176 initial_fractional_depth, reservoir_type)
178 this%pointer_allocation_guard = .true.
180 ! Initialize persistence weight index to be out of bounds of the persistence weights array
181 this%state%persistence_weight_index = SIZE(this%properties%persistence_weighted_coefficients) + 1
183 ! Output warning if the initial storage is greater than max storage or less than zero.
184 if (this%state%current_storage > this%properties%max_storage) then
185 this%state%current_storage = this%properties%max_storage
186 write(lake_number_string, "(I15)") lake_number
187 write(log_warning,*) "WARNING: initial storage exceeds max storage for reservoir ", lake_number_string, &
188 ". Setting initial storage to max storage."
190 else if (this%state%current_storage < this%properties%min_storage) then
191 this%state%current_storage = this%properties%min_storage
192 write(lake_number_string, "(I15)") lake_number
193 write(log_warning,*) "WARNING: initial storage is less than zero for reservoir ", lake_number_string, &
194 ". Setting initial storage to zero."
198 ! Allocate a single level pool reservoir
199 allocate(levelpool :: this%state%levelpool_ptr)
201 ! Initialize level pool reservoir
202 call this%state%levelpool_ptr%init(water_elevation, lake_area, &
203 weir_elevation, weir_coeffecient, weir_length, dam_length, orifice_elevation, &
204 orifice_coefficient, orifice_area, lake_max_water_elevation, lake_number)
207 end subroutine hybrid_init
211 subroutine hybrid_destroy(this)
213 class(persistence_levelpool_hybrid), intent(inout) :: this ! object being destroyed
214 end subroutine hybrid_destroy
217 ! Subroutine for running reservoir for a hybrid reservoir
218 subroutine run_hybrid_reservoir(this, previous_timestep_inflow, inflow, &
219 lateral_inflow, water_elevation, outflow, routing_period, dynamic_reservoir_type, &
220 assimilated_value, assimilated_source_file)
222 class(persistence_levelpool_hybrid), intent(inout) :: this
223 real, intent(in) :: previous_timestep_inflow ! cubic meters per second (cms)
224 real, intent(in) :: inflow ! cubic meters per second (cms)
225 real, intent(in) :: lateral_inflow ! cubic meters per second (cms)
226 real, intent(inout) :: water_elevation ! meters
227 real, intent(out) :: outflow ! cubic meters per second (cms)
228 real, intent(in) :: routing_period ! seconds
229 integer, intent(out):: dynamic_reservoir_type ! dynamic reservoir type sent to lake out files
230 real, intent(out) :: assimilated_value ! value assimilated from observation
231 character(len=256), intent(out) :: assimilated_source_file ! source file of assimilated value
232 real :: delta_storage ! timestep change in storage (cubic meters)
233 real :: local_water_elevation ! water elevation passed to levelpool (meters AMSL)
234 real :: levelpool_outflow ! cubic meters per second (cms)
235 logical :: max_storage_reached ! flag for when max storage is reached
236 integer :: gage_lookback_seconds ! seconds between current model
237 ! time and the time a gage
240 max_storage_reached = .false.
241 gage_lookback_seconds = 0
243 ! Update input variables
244 this%input%inflow = inflow
245 this%input%lateral_inflow = lateral_inflow
247 ! The initialization and management of water elevation might diverge between the global state of the model
248 ! and the internal state. The global state water elevation is passed to/from this subroutine at every
249 ! timestep. This check is to help guard against an external piece of code adjusting the global state water
250 ! elevation, which would cause it to no longer be synchronized with internal state water elevation. This
251 ! check also allows this module to borrow from the existing restart capability by initializing reservoirs
252 ! to some default water elevation and storage and then waiting for the first timestep to receive the
253 ! initial condition, so that the reservoir would not need to manage its own restart. If water elevation
254 ! is refactored to no longer be a global state, then this check is not necessary.
255 if (this%state%water_elevation .ne. water_elevation) then
257 ! Update state variables
258 this%state%water_elevation = water_elevation
260 this%state%current_storage = (this%state%water_elevation - this%properties%orifice_elevation) * &
261 this%properties%lake_area
265 local_water_elevation = this%state%water_elevation
267 ! If update time to read new timeslice gage discharges. In forecast modes, the update time automatically is set to
268 ! 1,000,000 to ensure that gage discharges are only read at the first timestep. For analysis runs, a timeslice
269 ! update time is set as a namelist parameter. A typical update time for an analysis run would be 3600 seconds
270 ! (1 hour), at which time new observations would be retrieved. The first reservoir for each processor to reach an
271 ! update time will call the function to read a new timeslice file, and these timeslice discharges will be held in
272 ! memory for the subsequent reservoirs to use at that timestep.
273 if (this%state%current_time >= this%state%timeslice_update_time) then
275 ! Read new timeslice gage discharges
276 call this%timeslice_data%setup_read_timeslice(this%properties%observation_update_time_interval_seconds, &
277 this%state%current_time, this%properties%gage_id, gage_lookback_seconds, this%state%gage_discharge, &
278 this%state%assimilated_source_file)
280 ! If no good quality gage discharge was found in the given lookback period, the gage discharge is returned
281 ! as -1.0. The persistence weight index will be set out of bounds larger than the array. This causes the
282 ! the weight update logic to set the persistence weight to 0.0. Therefore, only levelpool calculations will be used.
283 if (this%state%gage_discharge < 0.0) then
285 ! If at weight update time, then increment persistence weight index.
286 ! Otherwise, continue using previous persistence weight index.
287 if (this%state%current_time >= this%state%weight_update_time) then
288 ! Increment weight index
289 this%state%persistence_weight_index = this%state%persistence_weight_index + 1
291 ! Set next weight update time
292 this%state%weight_update_time = this%state%weight_update_time + this%properties%weight_update_time_interval
295 ! If out of bounds of array, then set persistence weight to 0.0. Otherwise, set persistence weight from array's indexed value.
296 if (this%state%persistence_weight_index > SIZE(this%properties%persistence_weighted_coefficients)) then
297 this%state%persistence_current_weight = 0.0
300 this%state%persistence_current_weight = this%properties%persistence_weighted_coefficients(this%state%persistence_weight_index)
305 ! Set persisted outflow to gage discharge
306 this%state%persisted_outflow = this%state%gage_discharge
308 ! Set assimilated value to gage discharge
309 this%state%assimilated_value = this%state%gage_discharge
311 ! Start persistence weight index at 1
312 this%state%persistence_weight_index = 1
314 ! Grab first persistence weight at index 1 from array
315 this%state%persistence_current_weight = this%properties%persistence_weighted_coefficients(this%state%persistence_weight_index)
317 ! Set weight update time offset by how long back a timeslice discharge was read. For instance, if the weight update interval is
318 ! 24 hours, and the gage discharge was read 2 hours back, then the weight update time will be set to 22 hours after the current time.
319 this%state%weight_update_time = this%state%current_time + this%properties%weight_update_time_interval - gage_lookback_seconds
323 ! Calculate levelpool weight
324 this%state%levelpool_current_weight = 1.0 - this%state%persistence_current_weight
326 ! Set timeslice update time
327 this%state%timeslice_update_time = this%state%timeslice_update_time + this%properties%observation_update_time_interval_seconds
329 ! If update time to change persistence weights
330 else if (this%state%current_time >= this%state%weight_update_time) then
332 ! Increment weight index
333 this%state%persistence_weight_index = this%state%persistence_weight_index + 1
335 ! Boundary check to not exceed the size of the persistence weights array
336 if (this%state%persistence_weight_index <= SIZE(this%properties%persistence_weighted_coefficients)) then
338 ! Grab indexed persistence weight from array
339 this%state%persistence_current_weight = this%properties%persistence_weighted_coefficients(this%state%persistence_weight_index)
342 ! If boundary of persistence weights array has been exceeded, then set all persistence weights to 0.0
343 this%state%persistence_current_weight = 0.0
347 ! Calculate levelpool weight
348 this%state%levelpool_current_weight = 1.0 - this%state%persistence_current_weight
350 ! Set next weight update time
351 this%state%weight_update_time = this%state%weight_update_time + this%properties%weight_update_time_interval
355 ! Referencing issue https://github.com/NCAR/wrf_hydro_nwm_public/issues/326 for the uncertainty about
356 ! previous_timestep_inflow. Further understanding of the exact timing of inflow and previous timestep
357 ! inflow might affect which values are passed to this calling of levelpool and subsequent mass/balance
359 ! Run levelpool reservoir
360 call this%state%levelpool_ptr%run(previous_timestep_inflow, inflow, &
361 lateral_inflow, local_water_elevation, levelpool_outflow, routing_period, this%state%levelpool_reservoir_type, &
362 this%state%levelpool_assimilated_value, this%state%levelpool_assimilated_source_file)
364 ! If the levelpool weight is within epsilon of 1.0
365 if (this%state%levelpool_current_weight .ge. 1.0 - epsilon(1.0)) then
367 ! Set dynamic_reservoir_type to levelpool type
368 this%state%dynamic_reservoir_type = this%state%levelpool_reservoir_type
370 ! Set the assimilated_value to sentinel, -9999.0
371 this%state%assimilated_value = -9999.0
373 ! Set the assimilated_source_file to empty string
374 this%state%assimilated_source_file = ""
377 ! Set dynamic_reservoir_type to given USGS or USACE type
378 this%state%dynamic_reservoir_type = this%properties%reservoir_type
381 ! Calculate outflow weighted between persistence and levelpool
382 this%output%outflow = this%state%persistence_current_weight &
383 * this%state%persisted_outflow + this%state%levelpool_current_weight &
386 ! Warn if there is a negative inflow
387 call warn_negative_inflow(this%input%inflow, this%properties%lake_number, this%state%current_time)
389 ! Modify if exceeding storage boundary conditions
390 call modify_for_projected_storage(this%input%inflow, &
391 this%state%current_storage, this%properties%min_storage, &
392 this%properties%max_storage, this%properties%lake_number, &
393 this%state%current_time, int(routing_period), this%output%outflow, max_storage_reached)
395 ! If max storage has been reached, release greater of persistence or levelpool
396 if (max_storage_reached .and. this%output%outflow < levelpool_outflow) then
397 this%output%outflow = levelpool_outflow
399 ! Set dynamic_reservoir_type to levelpool type
400 this%state%dynamic_reservoir_type = this%state%levelpool_reservoir_type
402 ! Set the assimilated_value to sentinel, -9999.0
403 this%state%assimilated_value = -9999.0
405 ! Set the assimilated_source_file to empty string
406 this%state%assimilated_source_file = ""
409 ! Calculate change in storage
410 delta_storage = (this%input%inflow - this%output%outflow) * routing_period
412 ! Update storage from the most recent model states
413 this%state%current_storage = this%state%current_storage + delta_storage
415 ! Calculate new water elevation. Delta storage is used as opposed to calculating from current storage in
416 ! order to minimize floating point error because current storage is a much larger magnitude than water elevation.
417 this%state%water_elevation = this%state%water_elevation + delta_storage / this%properties%lake_area
419 ! Update output variable returned from this subroutine
420 outflow = this%output%outflow
422 ! Set current inflow to previous_timestep_inflow
423 this%input%previous_timestep_inflow = inflow
425 ! Update water_elevation variable returned from this subroutine
426 water_elevation = this%state%water_elevation
428 ! Update the dynamic_reservoir_type returned from this subroutine
429 dynamic_reservoir_type = this%state%dynamic_reservoir_type
431 ! Update the assimilated_value returned from this subroutine
432 assimilated_value = this%state%assimilated_value
434 ! Update the assimilated_source_file returned from this subroutine
435 assimilated_source_file = this%state%assimilated_source_file
437 ! Update the current time
438 this%state%current_time = this%state%current_time + int(routing_period)
441 ! Log diagnostic data only for development/debugging purposes
442 call log_hybrid_diagnostic_data(this%properties%lake_number, this%state%current_time, &
443 this%state%timeslice_update_time, this%state%weight_update_time, gage_lookback_seconds, &
444 this%properties%gage_id, this%state%persistence_weight_index, &
445 this%state%persistence_current_weight, this%input%inflow, this%state%current_storage, &
446 this%state%water_elevation, this%state%gage_discharge, this%state%persisted_outflow, &
447 levelpool_outflow, this%output%outflow)
450 end subroutine run_hybrid_reservoir
452 end module module_persistence_levelpool_hybrid