1 ! This module contains a variety of subroutines/functions
2 ! used by reservoir objects.
4 module module_reservoir_utilities
6 use module_hydro_stop, only: HYDRO_stop
7 use iso_fortran_env, only: int64
11 integer, parameter :: log_warning = 6
13 integer, parameter :: log_warning = 78
18 ! Checks and warns for boundary conditions of negative inflow
19 subroutine warn_negative_inflow(inflow, lake_number, current_time)
20 real, intent(in) :: inflow
21 integer, intent(in) :: lake_number, current_time
23 if (inflow < 0.0) then
24 write(log_warning,*) "WARNING: Current inflow ", inflow, &
25 " cubic meters per second has reached below zero for reservoir ", &
26 lake_number, " at ", current_time, " seconds after model start time."
29 end subroutine warn_negative_inflow
32 ! Checks and warns for boundary conditions of exceeding max or min storage
33 subroutine warn_storage_boundaries(inflow, end_time_storage, min_storage, max_storage, lake_number, current_time)
34 real, intent(in) :: inflow, end_time_storage, min_storage, max_storage
35 integer, intent(in) :: lake_number, current_time
37 ! Also have option to set the end_time_storage to the max_storage and change the outflow accordingly.
38 if (end_time_storage > max_storage) then
40 write(log_warning,*) "WARNING: Calculated storage for this timestep, ", end_time_storage, &
41 " cubic meters, will exceed the maximum storage boundary of: ", max_storage, &
42 " cubic meters, for reservoir ", lake_number," at ", current_time, " seconds after model start time."
43 write(log_warning,*) "Current inflow is ", inflow, " cubic meters per second."
45 ! Also have option to set the end_time_storage to the min_storage and change the outflow accordingly.
46 else if (end_time_storage < min_storage .and. end_time_storage > 0.0) then
48 write(log_warning,*) "WARNING: Calculated storage for this timestep, ", end_time_storage, &
49 " cubic meters, will fall below the minimum storage requirement of: ", min_storage, &
50 " cubic meters, for reservoir ", lake_number, " at ", current_time, " seconds after model start time."
51 write(log_warning,*) "Current inflow is ", inflow, " cubic meters per second."
55 end subroutine warn_storage_boundaries
58 ! Checks and modifies for boundary conditions of negative storage
59 subroutine modify_negative_storage(inflow, previous_time_storage, routing_period, lake_number, current_time, &
60 update_time, end_time_storage, outflow)
61 real, intent(in) :: inflow, previous_time_storage, routing_period
62 integer, intent(in) :: lake_number, current_time, update_time
63 real, intent(inout) :: end_time_storage, outflow
66 ! In order to conserve mass, when a reservoir's storage would become 0.0 or less, the release
67 ! must be adjusted to avoid releasing water that is not available in the reservoir. To determine
68 ! conditions when this could happen, this function calculates the projected by the end of the
69 ! timestep (dt) based on the latest known conditions of the storage, inflow, and release values.
71 ! If the current conditions would cause the storage to become less than or equal to 0.0 cubic meters,
72 ! then the outflow/release will be altered to provide the available storage over the timestep
73 ! (given as R = (S + I*dt)/dt ). This will cause storage at the end of the timestep to be 0.0. From
74 ! this point, the outflow/release is bounded by the inflow provided until the next update time when
75 ! the machine learning model will be run again.
77 ! Therefore, the user's choice of time_interval has a significant impact on this simulated outflow/release
78 ! behavior in this edge case. Release cannot increase again until the next update time occurs. This does,
79 ! however, allow the reservoir to fill back up once inflow increases to exceed current outflow/release
80 ! since storage would then become > 0.0.
82 ! If one wants to allow the release to continue to rise as inflow rises, you can set R = I*dt once storage
83 ! has been depleted. This passes on inflow to outflow until an update time occurs, in which case the ML
84 ! model and its post conditions are responsible for calculating an appropriate release.
86 ! The logical 'and' below is not necessary because if otherwise this check updates outflow and
87 ! end_time_storage before an update time, those variables would be overwritten after a new release
88 ! is calculated from the machine learning model. The logical 'and' is left here for emphasis on
89 ! the importance it has on timesteps that are not also update times.
90 if (end_time_storage <= 0.0 .AND. current_time < update_time) then
92 write(log_warning,*) "WARNING: End time storage, ", end_time_storage, &
93 " cubic meters, has reached at or below 0.0 cubic meters for reservoir ", lake_number, &
94 " at ", current_time, " seconds after model start time."
95 write(log_warning,*) "Current inflow is ", inflow, " cubic meters per second."
97 outflow = (previous_time_storage + inflow * routing_period) / routing_period
99 end_time_storage = 0.0
101 if (outflow < 0.0) outflow = 0.0
105 end subroutine modify_negative_storage
108 ! Checks and modifies for boundary conditions of negative inflow
109 subroutine modify_negative_inflow(inflow)
110 real, intent(inout) :: inflow
112 if (inflow < 0.0) inflow = 0.0
114 end subroutine modify_negative_inflow
117 ! Checks and modifies for boundary conditions of negative outflow or exceeding min or max storage
118 subroutine modify_for_projected_storage(inflow, previous_time_storage, min_storage, max_storage, &
119 lake_number, current_time, time_interval, outflow, max_storage_reached)
120 real, intent(in) :: inflow, previous_time_storage, min_storage, max_storage
121 integer, intent(in) :: lake_number, current_time, time_interval
122 real, intent(inout) :: outflow
123 logical, intent(inout) :: max_storage_reached
124 real :: excess, projected_next_time_interval_storage
126 if (outflow < 0.0) then
127 write(log_warning,*) "WARNING: Calculations return a negative outflow for reservoir ", lake_number
128 write(log_warning,*) "at ", current_time, " seconds after model start time."
132 ! The projected_next_time_interval_storage is only calculated as a boundary condition to update the release
133 ! and this storage is calculated assuming a constant flow rate over the time interval. If the time interval
134 ! is too large relative to the channel routing period, this may not be an appropriate calculation.
135 projected_next_time_interval_storage = previous_time_storage + (inflow - outflow) * time_interval
137 if (projected_next_time_interval_storage > max_storage) then
139 max_storage_reached = .true.
141 write(log_warning,*) "WARNING: Modified release to prevent maximum storage exceedance", &
142 " for reservoir ", lake_number
143 write(log_warning,*) "at ", current_time, " seconds after model start time."
145 else if (projected_next_time_interval_storage < min_storage &
146 .AND. projected_next_time_interval_storage > 0.0) then
148 outflow = (projected_next_time_interval_storage - min_storage) / time_interval + outflow
150 write(log_warning,*) "WARNING: Modified release to maintain minimum storage for reservoir ", lake_number
151 write(log_warning,*) "at ", current_time, " seconds after model start time."
153 else if (projected_next_time_interval_storage <= 0.0) then
157 write(log_warning,*) "WARNING: Modified release to prevent storage deficit for reservoir ", lake_number
158 write(log_warning,*) "at ", current_time, " seconds after model start time."
162 if (outflow < 0.0) outflow = 0.0
164 end subroutine modify_for_projected_storage
166 ! Reads the reservoir_type form the reservoir parameter file. This subroutine first checks to ensure that
167 ! the lake_id array in the reservoir parameter file matches the lake_id array in the lake parameter file and
168 ! calls hydro_stop if they do not match.
169 subroutine read_reservoir_type(reservoir_parameter_file, lake_parameter_lake_ids, number_of_lake_parameter_lakes, &
171 character(len=*), intent(in) :: reservoir_parameter_file
172 integer(kind=int64), dimension(:), intent(in) :: lake_parameter_lake_ids
173 integer, intent(in) :: number_of_lake_parameter_lakes
174 integer, dimension(:), intent(inout) :: reservoir_types
175 integer(kind=int64), allocatable, dimension(:) :: reservoir_parameter_lake_ids
177 integer, dimension(nf90_max_var_dims) :: dim_ids
178 integer :: ncid, var_id, lake_index, number_of_reservoirs, number_of_reservoir_types
179 integer :: status ! status of reading NetCDF
181 ! Open Reservoir Parameter NetCDF file
182 status = nf90_open(path = reservoir_parameter_file, mode = nf90_nowrite, ncid = ncid)
183 if (status /= nf90_noerr) call handle_err(status, "Could not open reservoir parameter file " &
184 // trim(ADJUSTL(reservoir_parameter_file)) // ".")
186 status = nf90_inq_varid(ncid, "lake_id", var_id)
187 if (status /= nf90_noerr) call handle_err(status, "lake_id read error in reservoir parameter file " &
188 // trim(ADJUSTL(reservoir_parameter_file)) // ".")
190 status = nf90_inquire_variable(ncid, var_id, dimids = dim_ids)
191 if(status /= nf90_NoErr) call handle_err(status, "lake_id read error in reservoir parameter file " &
192 // trim(ADJUSTL(reservoir_parameter_file)) // ".")
194 status = nf90_inquire_dimension(ncid, dim_ids(1), len = number_of_reservoirs)
195 if(status /= nf90_NoErr) call handle_err(status, "lake_id read error in reservoir parameter file " &
196 // trim(ADJUSTL(reservoir_parameter_file)) // ".")
198 allocate(reservoir_parameter_lake_ids(number_of_reservoirs))
200 status = nf90_get_var(ncid, var_id, reservoir_parameter_lake_ids)
201 if (status /= nf90_noerr) call handle_err(status, "lake_id read error in reservoir parameter file " &
202 // trim(ADJUSTL(reservoir_parameter_file)) // ".")
204 ! Check to ensure that the lake_id array size in the reservoir parameter file matches the lake_id array
205 ! size in the lake parameter file
206 if (number_of_lake_parameter_lakes .ne. number_of_reservoirs) then
207 call hydro_stop("ERROR: Reservoir ID array in the reservoir parameter file does not match the Lake ID array &
208 in the lake parameter file.")
211 ! Check to ensure that each lake id in the lake_id array in the reservoir parameter file matches
212 ! each lake id in the lake_id array in the lake parameter file
213 do lake_index = 1, number_of_reservoirs
215 if (lake_parameter_lake_ids(lake_index) .ne. reservoir_parameter_lake_ids(lake_index)) then
216 call hydro_stop("Reservoir ID array in the reservoir parameter file does not match the Lake ID array &
217 in the lake parameter file.")
222 status = nf90_inq_varid(ncid, "reservoir_type", var_id)
223 if (status /= nf90_noerr) call handle_err(status, "reservoir_type read error in reservoir parameter file " &
224 // trim(ADJUSTL(reservoir_parameter_file)) // ".")
226 status = nf90_inquire_variable(ncid, var_id, dimids = dim_ids)
227 if(status /= nf90_NoErr) call handle_err(status, "reservoir_type read error in reservoir parameter file " &
228 // trim(ADJUSTL(reservoir_parameter_file)) // ".")
230 status = nf90_inquire_dimension(ncid, dim_ids(1), len = number_of_reservoir_types)
231 if(status /= nf90_NoErr) call handle_err(status, "reservoir_type read error in reservoir parameter file " &
232 // trim(ADJUSTL(reservoir_parameter_file)) // ".")
234 ! Check to ensure that the reservoir_type array size matches the lake_id array size.
235 if (number_of_reservoir_types .ne. number_of_lake_parameter_lakes) then
236 call hydro_stop("ERROR: Reservoir Type array size in the reservoir parameter file does not match the Lake ID array size &
237 in the lake parameter file.")
240 status = nf90_get_var(ncid, var_id, reservoir_types)
241 if (status /= nf90_noerr) call handle_err(status, "reservoir_type read error in reservoir parameter file " &
242 // trim(ADJUSTL(reservoir_parameter_file)) // ".")
244 ! Close Reservoir Parameter NetCDF file
245 status = nf90_close(ncid)
246 if (status /= nf90_noerr) call handle_err(status, "Could not close reservoir parameter file " &
247 // trim(ADJUSTL(reservoir_parameter_file)) // ".")
249 if(allocated(reservoir_parameter_lake_ids)) deallocate(reservoir_parameter_lake_ids)
251 end subroutine read_reservoir_type
254 ! Gets the dimensions of the gage arrays from the timeslice file
255 subroutine get_timeslice_array_dimension(ncid, netcdf_array_name, timeslice_file, number_of_gages)
256 integer, intent(in) :: ncid
257 character(len=*), intent(in) :: netcdf_array_name
258 character(len=256), intent(in) :: timeslice_file
259 integer, intent(out) :: number_of_gages
260 integer :: var_id, status
261 integer, dimension(nf90_max_var_dims) :: dim_ids
263 status = nf90_inq_varid(ncid, netcdf_array_name, var_id)
264 if (status /= nf90_noerr) call handle_err(status, netcdf_array_name // " read error in timeslice file " &
265 // trim(ADJUSTL(timeslice_file)) // ".")
267 status = nf90_inquire_variable(ncid, var_id, dimids = dim_ids)
268 if(status /= nf90_NoErr) call handle_err(status, netcdf_array_name // " read error in timeslice file " &
269 // trim(ADJUSTL(timeslice_file)) // ".")
271 status = nf90_inquire_dimension(ncid, dim_ids(1), len = number_of_gages)
272 if(status /= nf90_NoErr) call handle_err(status, netcdf_array_name // " read error in timeslice file " &
273 // trim(ADJUSTL(timeslice_file)) // ".")
275 end subroutine get_timeslice_array_dimension
278 ! Read gage id array from the timeslice NetCDF
279 subroutine read_timeslice_gage_ids(ncid, netcdf_array_name, timeslice_file, gage_id_var_array)
280 integer, intent(in) :: ncid
281 character(len=*), intent(in) :: netcdf_array_name
282 character(len=256), intent(in) :: timeslice_file
283 character(len=*), dimension(:), intent(out) :: gage_id_var_array
284 integer :: var_id, status
286 status = nf90_inq_varid(ncid, netcdf_array_name, var_id)
287 if (status /= nf90_noerr) call handle_err(status, netcdf_array_name // " read error in timeslice file " &
288 // trim(ADJUSTL(timeslice_file)) // ".")
290 status = nf90_get_var(ncid, var_id, gage_id_var_array)
291 if (status /= nf90_noerr) call handle_err(status, netcdf_array_name // " read error in timeslice file " &
292 // trim(ADJUSTL(timeslice_file)) // ".")
294 end subroutine read_timeslice_gage_ids
297 ! Read real one dimension parameter arrays from the timeslice NetCDF
298 subroutine read_timeslice_netcdf_real_1D_variables(ncid, netcdf_array_name, timeslice_file, real_var_array)
299 integer, intent(in) :: ncid
300 character(len=*), intent(in) :: netcdf_array_name
301 character(len=256), intent(in) :: timeslice_file
302 real, dimension(:), intent(out) :: real_var_array
303 integer :: var_id, status
305 status = nf90_inq_varid(ncid, netcdf_array_name, var_id)
306 if (status /= nf90_noerr) call handle_err(status, netcdf_array_name // " read error in timeslice file " &
307 // trim(ADJUSTL(timeslice_file)) // ".")
309 status = nf90_get_var(ncid, var_id, real_var_array)
310 if (status /= nf90_noerr) call handle_err(status, netcdf_array_name // " read error in timeslice file " &
311 // trim(ADJUSTL(timeslice_file)) // ".")
313 end subroutine read_timeslice_netcdf_real_1D_variables
316 ! Read integer one dimension parameter arrays from the timeslice NetCDF
317 subroutine read_timeslice_netcdf_integer_1D_variables(ncid, netcdf_array_name, timeslice_file, integer_var_array)
318 integer, intent(in) :: ncid
319 character(len=*), intent(in) :: netcdf_array_name
320 character(len=256), intent(in) :: timeslice_file
321 integer, dimension(:), intent(out) :: integer_var_array
322 integer :: var_id, status
324 status = nf90_inq_varid(ncid, netcdf_array_name, var_id)
325 if (status /= nf90_noerr) call handle_err(status, netcdf_array_name // " read error in timeslice file " &
326 // trim(ADJUSTL(timeslice_file)) // ".")
328 status = nf90_get_var(ncid, var_id, integer_var_array)
329 if (status /= nf90_noerr) call handle_err(status, netcdf_array_name // " read error in timeslice file " &
330 // trim(ADJUSTL(timeslice_file)) // ".")
332 end subroutine read_timeslice_netcdf_integer_1D_variables
335 ! Read all gage ids from reservoir parameter file
336 subroutine read_persistence_netcdf_gage_ids_all(ncid, netcdf_array_name, reservoir_parameter_file, var_id, number_of_gages)
337 integer, intent(in) :: ncid
338 character(len=*), intent(in) :: netcdf_array_name
339 character(len=*), intent(in) :: reservoir_parameter_file
340 integer, intent(out) :: var_id, number_of_gages
342 integer, dimension(nf90_max_var_dims) :: dim_ids
344 status = nf90_inq_varid(ncid, netcdf_array_name, var_id)
345 if (status /= nf90_noerr) call handle_err(status, netcdf_array_name // " read error in reservoir parameter file " &
346 // trim(ADJUSTL(reservoir_parameter_file)) // ".")
348 status = nf90_inquire_variable(ncid, var_id, dimids = dim_ids)
349 if(status /= nf90_NoErr) call handle_err(status, netcdf_array_name // " read error in reservoir parameter file " &
350 // trim(ADJUSTL(reservoir_parameter_file)) // ".")
352 status = nf90_inquire_dimension(ncid, dim_ids(2), len = number_of_gages)
353 if(status /= nf90_NoErr) call handle_err(status, netcdf_array_name // " read error in reservoir parameter file " &
354 // trim(ADJUSTL(reservoir_parameter_file)) // ".")
356 end subroutine read_persistence_netcdf_gage_ids_all
359 ! Read lake id from reservoir parameter file and determine the lake_id_index, which
360 ! is the index of the particular reservoir's values in all of the parameter arrays.
361 subroutine read_netcdf_lake_id(ncid, lake_number, netcdf_array_name, reservoir_parameter_file, lake_id_index)
362 integer, intent(in) :: ncid
363 integer(kind=int64), intent(in) :: lake_number
364 character(len=*), intent(in) :: netcdf_array_name
365 character(len=*), intent(in) :: reservoir_parameter_file
366 integer, intent(out) :: lake_id_index
367 integer, allocatable, dimension(:) :: temp_lake_id_array
368 integer :: var_id, status, number_of_lakes, lake_index
369 integer, dimension(nf90_max_var_dims) :: dim_ids
371 status = nf90_inq_varid(ncid, netcdf_array_name, var_id)
372 if (status /= nf90_noerr) call handle_err(status, netcdf_array_name // " read error in reservoir parameter file " &
373 // trim(ADJUSTL(reservoir_parameter_file)) // ".")
375 status = nf90_inquire_variable(ncid, var_id, dimids = dim_ids)
376 if(status /= nf90_NoErr) call handle_err(status, netcdf_array_name)
378 status = nf90_inquire_dimension(ncid, dim_ids(1), len = number_of_lakes)
379 if(status /= nf90_NoErr) call handle_err(status, netcdf_array_name // " read error in reservoir parameter file " &
380 // trim(ADJUSTL(reservoir_parameter_file)) // ".")
382 allocate(temp_lake_id_array(number_of_lakes))
384 status = nf90_get_var(ncid, var_id, temp_lake_id_array)
385 if (status /= nf90_noerr) call handle_err(status, netcdf_array_name // " read error in reservoir parameter file " &
386 // trim(ADJUSTL(reservoir_parameter_file)) // ".")
388 do lake_index = 1, number_of_lakes
389 if (temp_lake_id_array(lake_index) == lake_number) then
390 lake_id_index = lake_index
394 if(allocated(temp_lake_id_array)) deallocate(temp_lake_id_array)
396 end subroutine read_netcdf_lake_id
399 ! Read gage id array from reservoir parameter file
400 subroutine read_persistence_netcdf_gage_id(ncid, lake_id_index, netcdf_array_name, reservoir_parameter_file, gage_id)
401 integer, intent(in) :: ncid, lake_id_index
402 character(len=*), intent(in) :: netcdf_array_name
403 character(len=*), intent(in) :: reservoir_parameter_file
404 character(len=*), intent(out) :: gage_id
405 character(len=15) :: gage_id_string, gage_id_string_trimmed
406 character(len=15), allocatable, dimension(:) :: temp_gage_id_array
407 integer :: var_id, status, number_of_lakes
408 integer, dimension(nf90_max_var_dims) :: dim_ids
409 integer :: char_index, number_counter, temp_val
410 character(len=1) :: temp_char
412 status = nf90_inq_varid(ncid, netcdf_array_name, var_id)
413 if (status /= nf90_noerr) call handle_err(status, netcdf_array_name // " read error in reservoir parameter file " &
414 // trim(ADJUSTL(reservoir_parameter_file)) // ".")
416 status = nf90_inquire_variable(ncid, var_id, dimids = dim_ids)
417 if(status /= nf90_NoErr) call handle_err(status, netcdf_array_name // " read error in reservoir parameter file " &
418 // trim(ADJUSTL(reservoir_parameter_file)) // ".")
420 status = nf90_inquire_dimension(ncid, dim_ids(2), len = number_of_lakes)
421 if(status /= nf90_NoErr) call handle_err(status, netcdf_array_name // " read error in reservoir parameter file " &
422 // trim(ADJUSTL(reservoir_parameter_file)) // ".")
424 allocate(temp_gage_id_array(number_of_lakes))
426 status = nf90_get_var(ncid, var_id, temp_gage_id_array)
427 if (status /= nf90_noerr) call handle_err(status, netcdf_array_name // " read error in reservoir parameter file " &
428 // trim(ADJUSTL(reservoir_parameter_file)) // ".")
430 gage_id_string = temp_gage_id_array(lake_id_index)
432 do char_index = 1, 15
433 temp_val = ichar(gage_id_string(char_index:char_index))
435 ! Check if null character, then pad with a space
436 if (temp_val == 0) then
437 gage_id_string(char_index:char_index) = ' '
441 gage_id_string_trimmed = ADJUSTL(trim(gage_id_string))
443 gage_id = gage_id_string_trimmed
445 if(allocated(temp_gage_id_array)) deallocate(temp_gage_id_array)
447 end subroutine read_persistence_netcdf_gage_id
450 ! Read rfc gage id array from the reservoir parameters file
451 subroutine read_reservoir_parameters_netcdf_rfc_gage_id(ncid, lake_id_index, &
452 netcdf_array_name, reservoir_parameter_file, rfc_gage_id)
453 integer, intent(in) :: ncid, lake_id_index
454 character(len=*), intent(in) :: netcdf_array_name
455 character(len=*), intent(in) :: reservoir_parameter_file
456 character(len=5), intent(out) :: rfc_gage_id
457 character(len=5) :: gage_id_string, gage_id_string_trimmed
458 character(len=5), allocatable, dimension(:) :: temp_gage_id_array
459 integer :: var_id, status, number_of_lakes
460 integer, dimension(nf90_max_var_dims) :: dim_ids
461 integer :: char_index, number_counter
462 character(len=1) :: temp_char
464 status = nf90_inq_varid(ncid, netcdf_array_name, var_id)
465 if (status /= nf90_noerr) call handle_err(status, netcdf_array_name // " read error in reservoir parameter file " &
466 // trim(ADJUSTL(reservoir_parameter_file)) // ".")
468 status = nf90_inquire_variable(ncid, var_id, dimids = dim_ids)
469 if(status /= nf90_NoErr) call handle_err(status, netcdf_array_name)
471 status = nf90_inquire_dimension(ncid, dim_ids(2), len = number_of_lakes)
472 if(status /= nf90_NoErr) call handle_err(status, netcdf_array_name // " read error in reservoir parameter file " &
473 // trim(ADJUSTL(reservoir_parameter_file)) // ".")
475 allocate(temp_gage_id_array(number_of_lakes))
477 status = nf90_get_var(ncid, var_id, temp_gage_id_array)
478 if (status /= nf90_noerr) call handle_err(status, netcdf_array_name // " read error in reservoir parameter file " &
479 // trim(ADJUSTL(reservoir_parameter_file)) // ".")
481 gage_id_string = temp_gage_id_array(lake_id_index)
483 gage_id_string_trimmed = ADJUSTL(trim(gage_id_string))
485 rfc_gage_id = gage_id_string_trimmed
487 if(allocated(temp_gage_id_array)) deallocate(temp_gage_id_array)
489 end subroutine read_reservoir_parameters_netcdf_rfc_gage_id
492 ! Read an RFC forecast integer variable for a corresponding lake id from the reservoir parameter file
493 subroutine read_reservoir_parameters_netcdf_rfc_integer(ncid, lake_id_index, &
494 netcdf_array_name, reservoir_parameter_file, integer_var)
495 integer, intent(in) :: ncid, lake_id_index
496 character(len=*), intent(in) :: netcdf_array_name
497 character(len=*), intent(in) :: reservoir_parameter_file
498 integer, intent(out) :: integer_var
499 integer, allocatable, dimension(:) :: temp_integer_array
500 integer :: var_id, status, number_of_lakes
501 integer, dimension(nf90_max_var_dims) :: dim_ids
503 status = nf90_inq_varid(ncid, netcdf_array_name, var_id)
504 if (status /= nf90_noerr) call handle_err(status, netcdf_array_name // " read error in reservoir parameter file " &
505 // trim(ADJUSTL(reservoir_parameter_file)) // ".")
507 status = nf90_inquire_variable(ncid, var_id, dimids = dim_ids)
508 if(status /= nf90_NoErr) call handle_err(status, netcdf_array_name // " read error in reservoir parameter file " &
509 // trim(ADJUSTL(reservoir_parameter_file)) // ".")
511 status = nf90_inquire_dimension(ncid, dim_ids(1), len = number_of_lakes)
512 if(status /= nf90_NoErr) call handle_err(status, netcdf_array_name // " read error in reservoir parameter file " &
513 // trim(ADJUSTL(reservoir_parameter_file)) // ".")
515 allocate(temp_integer_array(number_of_lakes))
517 status = nf90_get_var(ncid, var_id, temp_integer_array)
518 if (status /= nf90_noerr) call handle_err(status, netcdf_array_name // " read error in reservoir parameter file " &
519 // trim(ADJUSTL(reservoir_parameter_file)) // ".")
521 integer_var = temp_integer_array(lake_id_index)
523 if(allocated(temp_integer_array)) deallocate(temp_integer_array)
525 end subroutine read_reservoir_parameters_netcdf_rfc_integer
528 ! Read real two dimension parameter arrays from the reservoir parameter file
529 subroutine read_persistence_netcdf_real_2D_parameters(ncid, lake_id_index, &
530 netcdf_array_name, reservoir_parameter_file, var_id, number_of_weights, number_of_lakes)
531 integer, intent(in) :: ncid, lake_id_index
532 character(len=*), intent(in) :: netcdf_array_name
533 character(len=*), intent(in) :: reservoir_parameter_file
534 integer, intent(out) :: var_id, number_of_weights, number_of_lakes
536 integer, dimension(nf90_max_var_dims) :: dim_ids
538 status = nf90_inq_varid(ncid, netcdf_array_name, var_id)
539 if (status /= nf90_noerr) call handle_err(status, netcdf_array_name // " read error in reservoir parameter file " &
540 // trim(ADJUSTL(reservoir_parameter_file)) // ".")
542 status = nf90_inquire_variable(ncid, var_id, dimids = dim_ids)
543 if(status /= nf90_NoErr) call handle_err(status, netcdf_array_name // " read error in reservoir parameter file " &
544 // trim(ADJUSTL(reservoir_parameter_file)) // ".")
546 status = nf90_inquire_dimension(ncid, dim_ids(1), len = number_of_weights)
547 if(status /= nf90_NoErr) call handle_err(status, netcdf_array_name // " read error in reservoir parameter file " &
548 // trim(ADJUSTL(reservoir_parameter_file)) // ".")
550 status = nf90_inquire_dimension(ncid, dim_ids(2), len = number_of_lakes)
551 if(status /= nf90_NoErr) call handle_err(status, netcdf_array_name // " read error in reservoir parameter file " &
552 // trim(ADJUSTL(reservoir_parameter_file)) // ".")
554 end subroutine read_persistence_netcdf_real_2D_parameters
557 ! Read integer parameters from the RFC Time Series NetCDF
558 subroutine read_timeslice_netcdf_integer_variables(ncid, netcdf_variable_name, timeslice_file, integer_variable)
559 integer, intent(in) :: ncid
560 character(len=*), intent(in) :: netcdf_variable_name
561 character(len=256), intent(in) :: timeslice_file
562 integer, intent(out) :: integer_variable
563 integer :: var_id, status
565 status = nf90_inq_varid(ncid, netcdf_variable_name, var_id)
566 if (status /= nf90_noerr) call handle_err(status, netcdf_variable_name // " read error in timeslice file " &
567 // trim(ADJUSTL(timeslice_file)) // ".")
569 status = nf90_get_var(ncid, var_id, integer_variable)
570 if (status /= nf90_noerr) call handle_err(status, netcdf_variable_name // " read error in timeslice file " &
571 // trim(ADJUSTL(timeslice_file)) // ".")
573 end subroutine read_timeslice_netcdf_integer_variables
576 ! Handle NetCDF error
577 subroutine handle_err(status, variable_error)
579 integer, intent (in) :: status
580 character (len=*), intent(in) :: variable_error
581 if(status /= nf90_noerr) then
582 call hydro_stop("ERROR: " // trim(nf90_strerror(status)) // ': ' // variable_error)
585 end subroutine handle_err
588 ! Create Persistence Levelpool Hybrid Reservoir Function Diagnostic Log CSV File
589 subroutine create_hybrid_diagnostic_log_file(lake_number)
590 integer, intent(in) :: lake_number
591 character(len=15) :: lake_number_string, lake_number_string_trimmed
592 character(len=50) :: filename_string
594 write(lake_number_string, "(I15)") lake_number
595 lake_number_string_trimmed = ADJUSTL(trim(lake_number_string))
596 filename_string = "hybrid_logs_"//ADJUSTL(trim(lake_number_string_trimmed))//".csv"
598 open (113, file=filename_string, status="unknown")
600 write (113, "(A256)") "Current Time, Timeslice Update Time, Weight Update Time, &
601 Gage Lookback Seconds, Gage ID, Current Persistence Weight Index, &
602 Current Persistence Weight, Inflow, Storage, Water Elevation, Gage Discharge, &
603 Persisted Outflow, Levelpool Outflow, Weighted Outflow"
607 end subroutine create_hybrid_diagnostic_log_file
610 ! Log data to Persistence Levelpool Hybrid Reservoir Function Diagnostic Log CSV File
611 subroutine log_hybrid_diagnostic_data(lake_number, current_time, timeslice_update_time, &
612 weight_update_time, gage_lookback_seconds, gage_id, persistence_weight_index, &
613 persistence_current_weight, inflow, current_storage, water_elevation, gage_discharge, &
614 persisted_outflow, levelpool_outflow, outflow)
615 integer, intent(in) :: lake_number, current_time, timeslice_update_time
616 integer, intent(in) :: weight_update_time, gage_lookback_seconds
617 integer, intent(in) :: persistence_weight_index
618 real, intent(in) :: persistence_current_weight, inflow, current_storage
619 real, intent(in) :: water_elevation, gage_discharge, persisted_outflow
620 real, intent(in) :: levelpool_outflow, outflow
621 character(len=*), intent(in) :: gage_id
622 character(len=15) :: lake_number_string, lake_number_string_trimmed
623 character(len=50) :: filename_string
625 write(lake_number_string, "(I15)") lake_number
626 lake_number_string_trimmed = ADJUSTL(trim(lake_number_string))
627 filename_string = "hybrid_logs_"//ADJUSTL(trim(lake_number_string_trimmed))//".csv"
629 open (113, file=filename_string, status="unknown", position = 'append')
631 write (113, "(I20, A1, I20, A1, I20, A1, I20, A1, A15, A1, I20, A1, F15.5, A1, F15.5, &
632 A1, F15.15, A1, F15.5, A1, F15.5, A1, F15.5, A1, F15.5, A1, F15.5)") &
633 current_time, ',', timeslice_update_time, ',', weight_update_time, ',', &
634 gage_lookback_seconds, ',', gage_id, ',', persistence_weight_index, ',', &
635 persistence_current_weight, ',', inflow, ',', current_storage, ',', &
636 water_elevation, ',', gage_discharge, ',', persisted_outflow, ',', levelpool_outflow, &
641 end subroutine log_hybrid_diagnostic_data
644 ! Create RFC Forecasts Reservoir Function Diagnostic Log CSV File
645 subroutine create_rfc_forecasts_diagnostic_log_file(lake_number)
646 integer, intent(in) :: lake_number
647 character(len=15) :: lake_number_string, lake_number_string_trimmed
648 character(len=50) :: filename_string
650 write(lake_number_string, "(I15)") lake_number
651 lake_number_string_trimmed = ADJUSTL(trim(lake_number_string))
652 filename_string = "rfc_forecasts_logs_"//ADJUSTL(trim(lake_number_string_trimmed))//".csv"
654 open (113, file=filename_string, status="unknown")
656 write (113, "(A256)") "Current_Time, Time_Step, Time_Series_Update_Time, &
657 Lookback_Seconds, Time_Series_Index, Gage_ID, Inflow, Water_Elevation, &
658 Levelpool_Outflow, Outflow"
662 end subroutine create_rfc_forecasts_diagnostic_log_file
665 ! Log data to RFC Forecasts Reservoir Function Diagnostic Log CSV File
666 subroutine log_rfc_forecasts_diagnostic_data(lake_number, current_time, time_step, &
667 time_series_update_time, lookback_seconds, time_series_index, gage_id, &
668 inflow, water_elevation, levelpool_outflow, outflow)
669 integer, intent(in) :: lake_number, current_time, time_step, time_series_update_time
670 integer, intent(in) :: lookback_seconds, time_series_index
671 character(len=*), intent(in) :: gage_id
672 real, intent(in) :: inflow, water_elevation, levelpool_outflow, outflow
673 character(len=15) :: lake_number_string, lake_number_string_trimmed
674 character(len=50) :: filename_string
676 write(lake_number_string, "(I15)") lake_number
677 lake_number_string_trimmed = ADJUSTL(trim(lake_number_string))
678 filename_string = "rfc_forecasts_logs_"//ADJUSTL(trim(lake_number_string_trimmed))//".csv"
680 open (113, file=filename_string, status="unknown", position = 'append')
682 write (113, "(I20, A1, I20, A1, I20, A1, I20, A1, I20, A1, A5, A1, F15.5, A1, &
683 F15.5, A1, F15.5, A1, F15.5)") &
684 current_time, ',', time_step, ',', time_series_update_time, ',', &
685 lookback_seconds, ',', time_series_index, ',', gage_id, ',', &
686 inflow, ',', water_elevation, ',', levelpool_outflow, ',', outflow
690 end subroutine log_rfc_forecasts_diagnostic_data
693 ! Create Levelpool Reservoir Function Diagnostic Log CSV File
694 subroutine create_levelpool_diagnostic_log_file(lake_number)
695 integer, intent(in) :: lake_number
696 character(len=15) :: lake_number_string, lake_number_string_trimmed
697 character(len=50) :: filename_string
699 write(lake_number_string, "(I15)") lake_number
700 lake_number_string_trimmed = ADJUSTL(trim(lake_number_string))
702 filename_string = "levelpool_logs_"//ADJUSTL(trim(lake_number_string_trimmed))//".csv"
704 open (113, file=filename_string, status="unknown")
706 write (113, "(A256)") "Inflow, Water Elevation, Outflow"
710 end subroutine create_levelpool_diagnostic_log_file
713 ! Log data to Levelpool Reservoir Function Diagnostic Log CSV File
714 subroutine log_levelpool_diagnostic_data(lake_number, inflow, water_elevation, outflow)
715 integer, intent(in) :: lake_number
716 real, intent(in) :: inflow
717 real, intent(in) :: water_elevation
718 real, intent(in) :: outflow
719 character(len=15) :: lake_number_string, lake_number_string_trimmed
720 character(len=50) :: filename_string
722 write(lake_number_string, "(I15)") lake_number
723 lake_number_string_trimmed = ADJUSTL(trim(lake_number_string))
724 filename_string = "levelpool_logs_"//ADJUSTL(trim(lake_number_string_trimmed))//".csv"
726 open (113, file=filename_string, status="unknown", position = 'append')
728 write (113, "(F15.8, A1, F15.8, A1, F15.8)") &
729 inflow, ',', water_elevation, ',', outflow
733 end subroutine log_levelpool_diagnostic_data
736 ! Get a new date given an existing date and time interval
737 subroutine geth_newdate (ndate, odate, idt)
740 ! From old date ("YYYY-MM-DD HH:MM:SS.ffff" or "YYYYMMDDHHMMSSffff") and
741 ! delta-time, compute the new date.
743 ! on entry - odate - the old hdate.
744 ! idt - the change in time
746 ! on exit - ndate - the new hdate.
748 integer, intent(in) :: idt
749 character (len=*), intent(out) :: ndate
750 character (len=*), intent(in) :: odate
754 ! yrold - indicates the year associated with "odate"
755 ! moold - indicates the month associated with "odate"
756 ! dyold - indicates the day associated with "odate"
757 ! hrold - indicates the hour associated with "odate"
758 ! miold - indicates the minute associated with "odate"
759 ! scold - indicates the second associated with "odate"
761 ! yrnew - indicates the year associated with "ndate"
762 ! monew - indicates the month associated with "ndate"
763 ! dynew - indicates the day associated with "ndate"
764 ! hrnew - indicates the hour associated with "ndate"
765 ! minew - indicates the minute associated with "ndate"
766 ! scnew - indicates the second associated with "ndate"
768 ! mday - a list assigning the number of days in each month
771 ! nday - the integer number of days represented by "idt"
772 ! nhour - the integer number of hours in "idt" after taking out
774 ! nmin - the integer number of minutes in "idt" after taking out
775 ! all the whole days and whole hours.
776 ! nsec - the integer number of minutes in "idt" after taking out
777 ! all the whole days, whole hours, and whole minutes.
779 integer :: newlen, oldlen
780 integer :: yrnew, monew, dynew, hrnew, minew, scnew, frnew
781 integer :: yrold, moold, dyold, hrold, miold, scold, frold
782 integer :: nday, nhour, nmin, nsec, nfrac, i, ifrc
784 character (len=10) :: hfrc
785 character (len=1) :: sp
787 integer :: yrstart, yrend, mostart, moend, dystart, dyend
788 integer :: hrstart, hrend, mistart, miend, scstart, scend, frstart
790 integer, dimension(12) :: mday = (/31,28,31,30,31,30,31,31,30,31,30,31/)
792 ! Determine if odate is "YYYY-MM-DD_HH ... " or "YYYYMMDDHH...."
793 if (odate(5:5) == "-") then
799 ! Break down old hdate into parts
847 write(*,*) 'FATAL ERROR: geth_newdate: odd length: #'//trim(odate)//'#'
848 call hydro_stop("geth_newdate")
852 if (oldlen.ge.11) then
901 write(*,*) 'FATAL ERROR: geth_newdate: odd length: #'//trim(odate)//'#'
902 call hydro_stop("geth_newdate")
907 ! Use internal READ statements to convert the CHARACTER string
908 ! date into INTEGER components.
910 read(odate(yrstart:yrend), '(i4)') yrold
911 read(odate(mostart:moend), '(i2)') moold
912 read(odate(dystart:dyend), '(i2)') dyold
914 read(odate(hrstart:hrend),'(i2)') hrold
916 read(odate(mistart:miend),'(i2)') miold
918 read(odate(scstart:scend),'(i2)') scold
920 read(odate(frstart:oldlen),*) frold
926 ! Set the number of days in February for that year.
928 mday(2) = nfeb(yrold)
930 ! Check that ODATE makes sense.
934 ! Check that the month of ODATE makes sense.
936 if ((moold.gt.12).or.(moold.lt.1)) then
937 write(*,*) 'GETH_NEWDATE: Month of ODATE = ', moold
941 ! Check that the day of ODATE makes sense.
943 if ((dyold.gt.mday(moold)).or.(dyold.lt.1)) then
944 write(*,*) 'GETH_NEWDATE: Day of ODATE = ', dyold
948 ! Check that the hour of ODATE makes sense.
950 if ((hrold.gt.23).or.(hrold.lt.0)) then
951 write(*,*) 'GETH_NEWDATE: Hour of ODATE = ', hrold
955 ! Check that the minute of ODATE makes sense.
957 if ((miold.gt.59).or.(miold.lt.0)) then
958 write(*,*) 'GETH_NEWDATE: Minute of ODATE = ', miold
962 ! Check that the second of ODATE makes sense.
964 if ((scold.gt.59).or.(scold.lt.0)) then
965 write(*,*) 'GETH_NEWDATE: Second of ODATE = ', scold
969 ! Check that the fractional part of ODATE makes sense.
974 write(*,*) 'FATAL ERROR: Crazy ODATE: ', odate(1:oldlen), oldlen
979 ! Date Checks are completed. Continue.
982 ! Compute the number of days, hours, minutes, and seconds in idt
984 if (units.ge.5) then !idt should be in fractions of seconds
985 ifrc = oldlen-(frstart)+1
987 nday = abs(idt)/(86400*ifrc)
988 nhour = mod(abs(idt),86400*ifrc)/(3600*ifrc)
989 nmin = mod(abs(idt),3600*ifrc)/(60*ifrc)
990 nsec = mod(abs(idt),60*ifrc)/(ifrc)
991 nfrac = mod(abs(idt), ifrc)
992 else if (units.eq.4) then !idt should be in seconds
994 nday = abs(idt)/86400 ! integer number of days in delta-time
995 nhour = mod(abs(idt),86400)/3600
996 nmin = mod(abs(idt),3600)/60
997 nsec = mod(abs(idt),60)
999 else if (units.eq.3) then !idt should be in minutes
1001 nday = abs(idt)/1440 ! integer number of days in delta-time
1002 nhour = mod(abs(idt),1440)/60
1003 nmin = mod(abs(idt),60)
1006 else if (units.eq.2) then !idt should be in hours
1008 nday = abs(idt)/24 ! integer number of days in delta-time
1009 nhour = mod(abs(idt),24)
1013 else if (units.eq.1) then !idt should be in days
1015 nday = abs(idt) ! integer number of days in delta-time
1022 write(*,'(''GETH_NEWDATE: Strange length for ODATE: '', i3)') &
1024 write(*,*) '#'//odate(1:oldlen)//'#'
1025 call hydro_stop("geth_newdate")
1031 frnew = frold + nfrac
1032 if (frnew.ge.ifrc) then
1033 frnew = frnew - ifrc
1037 scnew = scold + nsec
1038 if (scnew .ge. 60) then
1043 minew = miold + nmin
1044 if (minew .ge. 60) then
1049 hrnew = hrold + nhour
1050 if (hrnew .ge. 24) then
1060 if (dynew.gt.mday(monew)) then
1061 dynew = dynew - mday(monew)
1063 if (monew .gt. 12) then
1066 ! If the year changes, recompute the number of days in February
1067 mday(2) = nfeb(yrnew)
1072 else if (idt.lt.0) then
1074 frnew = frold - nfrac
1075 if (frnew .lt. 0) then
1076 frnew = frnew + ifrc
1080 scnew = scold - nsec
1081 if (scnew .lt. 00) then
1086 minew = miold - nmin
1087 if (minew .lt. 00) then
1092 hrnew = hrold - nhour
1093 if (hrnew .lt. 00) then
1103 if (dynew.eq.0) then
1105 if (monew.eq.0) then
1108 ! If the year changes, recompute the number of days in February
1109 mday(2) = nfeb(yrnew)
1116 ! Now construct the new mdate
1122 if (newlen.gt.frstart) then
1123 write(ndate(1:scend),19) yrnew, monew, dynew, hrnew, minew, scnew
1124 write(hfrc,'(i10)') frnew+1000000000
1125 ndate = ndate(1:scend)//'.'//hfrc(31-newlen:10)
1127 else if (newlen.eq.scend) then
1128 write(ndate(1:scend),19) yrnew, monew, dynew, hrnew, minew, scnew
1129 19 format(i4,'-',i2.2,'-',i2.2,'_',i2.2,':',i2.2,':',i2.2)
1131 else if (newlen.eq.miend) then
1132 write(ndate,16) yrnew, monew, dynew, hrnew, minew
1133 16 format(i4,'-',i2.2,'-',i2.2,'_',i2.2,':',i2.2)
1135 else if (newlen.eq.hrend) then
1136 write(ndate,13) yrnew, monew, dynew, hrnew
1137 13 format(i4,'-',i2.2,'-',i2.2,'_',i2.2)
1139 else if (newlen.eq.dyend) then
1140 write(ndate,10) yrnew, monew, dynew
1141 10 format(i4,'-',i2.2,'-',i2.2)
1147 if (newlen.gt.frstart) then
1148 write(ndate(1:scend),119) yrnew, monew, dynew, hrnew, minew, scnew
1149 write(hfrc,'(i10)') frnew+1000000000
1150 ndate = ndate(1:scend)//'.'//hfrc(31-newlen:10)
1152 else if (newlen.eq.scend) then
1153 write(ndate(1:scend),119) yrnew, monew, dynew, hrnew, minew, scnew
1154 119 format(i4,i2.2,i2.2,i2.2,i2.2,i2.2)
1156 else if (newlen.eq.miend) then
1157 write(ndate,116) yrnew, monew, dynew, hrnew, minew
1158 116 format(i4,i2.2,i2.2,i2.2,i2.2)
1160 else if (newlen.eq.hrend) then
1161 write(ndate,113) yrnew, monew, dynew, hrnew
1162 113 format(i4,i2.2,i2.2,i2.2)
1164 else if (newlen.eq.dyend) then
1165 write(ndate,110) yrnew, monew, dynew
1166 110 format(i4,i2.2,i2.2)
1172 if (punct .and. (oldlen.ge.11) .and. (newlen.ge.11)) ndate(11:11) = sp
1174 end subroutine geth_newdate
1176 integer function nfeb(year)
1178 ! Compute the number of days in February for the given year.
1181 integer, intent(in) :: year ! Four-digit year
1183 nfeb = 28 ! By default, February has 28 days ...
1184 if (mod(year,4).eq.0) then
1185 nfeb = 29 ! But every four years, it has 29 days ...
1186 if (mod(year,100).eq.0) then
1187 nfeb = 28 ! Except every 100 years, when it has 28 days ...
1188 if (mod(year,400).eq.0) then
1189 nfeb = 29 ! Except every 400 years, when it has 29 days ...
1190 if (mod(year,3600).eq.0) then
1191 nfeb = 28 ! Except every 3600 years, when it has 28 days.
1198 end module module_reservoir_utilities