Skip to content

Support sub-daily forcing time steps and units - #44

Open
CyrilThebault wants to merge 1 commit into
CH-Earth:stagingfrom
CyrilThebault:feature/hourly-timestep
Open

Support sub-daily forcing time steps and units#44
CyrilThebault wants to merge 1 commit into
CH-Earth:stagingfrom
CyrilThebault:feature/hourly-timestep

Conversation

@CyrilThebault

Copy link
Copy Markdown
Collaborator

Summary

This PR adds support for sub-daily forcing time steps and automatic conversion of input fluxes units.

Main changes

  • compute routing dimensions from the forcing time step
  • validate that forcing time coordinates are positive, increasing and regularly spaced
  • read NetCDF units independently for precipitation, PET and observed streamflow
  • automatically convert supported flux units to the internal mm/day representation
  • improve error messages for unsupported units

Supported units

Length:

  • mm
  • cm
  • dm
  • m

Time:

  • s
  • min
  • h
  • d

Both length/time and length per time syntaxes are supported (case-insensitive, with common aliases such as hour, hours, hr, etc.).

Testing

  • daily simulations reproduce previous results
  • hourly simulations reproduce the reference results
  • mixed units across forcing variables are handled correctly
  • unsupported units produce informative error messages

@martynpclark martynpclark left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking very good. I have a number of comments to improve code readability and functionality.

Comment on lines +362 to +371
! Syntax: length per time
ipos = index(units_lc, " per ")

if (ipos <= 1 .or. ipos + 4 > len_trim(units_lc)) then
ierr = 20
message = trim(message)// &
"unsupported flux units '"//trim(cunits)// &
"': expected length/time or length per time"
return
end if

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an unusual time unit. Fine it is supported.

end if
end do

! Current supported syntax: length/time or length per time

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would be better as a case statement. You can identify unique sub-strings and process accordingly. The most commonly used time units are likely L/T and L T-1. The best approach may be to first search for sub-strings, i.e., "/" and "-1" (and if you want "per", and then search for delimiters, i.e., "/", " ", and " per " for the three supported options. Then you can extract sub-strings for length and time. For generality it is probable best to have the sub-string extraction based on the length of the delimiter and avoid magic numbers in the code.

ierr = 20
message = trim(message)// &
"unsupported flux units '"//trim(cunits)// &
"': expected length/time"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We expect other flux units also.

if (ipos > 0) then

! Syntax: length/time
if (ipos <= 1 .or. ipos >= len_trim(units_lc)) then

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not understand why this erroe check is here since it is within the ipos>0 if statement. Also, can ipos ever be greater than len_trim(units_lc)?

"centimetre", "centimetres")
length_to_mm = 10._wp

case ("dm", "decimeter", "decimeters", &

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these units common? If not, then suggest delete this case to ensure we exit with an error.

case (iPRECIP); gForce_3d(:,:,1:numtim)%ppt = gTemp(:,:,:) * amult_ppt
case (iTEMP) ; gForce_3d(:,:,1:numtim)%temp = gTemp(:,:,:)
case (iPET) ; gForce_3d(:,:,1:numtim)%pet = gTemp(:,:,:)
case (iQOBS) ; aValid( :,:,1:numtim)%obsq = gTemp(:,:,:) ! TODO: check dimensions (works for nx=1, ny=1)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to retain this TODO comment somewhere.


subroutine get_forcing_varids(ncid, info, ierr, message)
use multiforce, only: amult_ppt, amult_pet, amult_q

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to move away from using shared variables in modules. This would be better stored in the info data structure.

! --------------------------------------------------------------------------------------

subroutine get_forcing_varids(ncid, info, ierr, message)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we need to change the name of the subroutine because it now does a lot more -- suggest get_forcing_metadata

Comment on lines +234 to +249
! Verify that the forcing time axis is increasing and regularly spaced.
do i = 3, size(jdate)
dt_current = (time_steps(i) - time_steps(i-1)) * scale_to_days

if (dt_current <= 0._wp) then
ierr = 1
message = trim(message)//"forcing time axis must be strictly increasing"
return
endif

if (abs(dt_current - deltim_days) > tolerance) then
ierr = 1
message = trim(message)//"forcing time steps are not equally spaced"
return
endif
enddo

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will increase compute time. Can we wrap this in a flag, e.g.,
logical(lgt), parameter :: do_timeCheck=.false.
and then
if(timeCheck)then
newcode
endif

It is possible that the extra time is minimal, and, if so, the flag can be =.true.

Comment on lines +90 to +113

! Compute the number of routing bins from the maximum routing horizon and the forcing timestep, both expressed in days.
NTDH = CEILING(TDH_MAX / info%time%deltim_days, KIND=I4B)

! Allocate the runoff fractions assigned to future routing bins.
ALLOCATE(DPARAM%FRAC_FUTURE(NTDH), STAT=ISTAT)
IF (ISTAT /= 0) THEN
ERR = 20
MESSAGE = TRIM(MESSAGE)//"cannot allocate DPARAM%FRAC_FUTURE"
RETURN
ENDIF

! Allocate the routing queue using the same number of bins.
ALLOCATE(FUTURE(NTDH), STAT=ISTAT)
IF (ISTAT /= 0) THEN
DEALLOCATE(DPARAM%FRAC_FUTURE)
ERR = 20
MESSAGE = TRIM(MESSAGE)//"cannot allocate FUTURE"
RETURN
ENDIF

! Initialise the routing queue before the first model evaluation.
FUTURE = 0._WP

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we put these allocate statements in q_timedelay? This will require passing the info structures through par_derive. We could also elevate the call to q_timedelay here.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making this comment because the intent is to keep the setup routines short and readable.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants