diff --git a/DESCRIPTION b/DESCRIPTION index d2d491493f047629dbb001538651c898f25caace..28d6655405fec6cf60b250661a87473352a6c032 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,8 +1,8 @@ Package: airGR Type: Package Title: Suite of GR Hydrological Models for Precipitation-Runoff Modelling -Version: 1.4.3.91 -Date: 2020-08-26 +Version: 1.4.4.2 +Date: 2020-10-14 Authors@R: c( person("Laurent", "Coron", role = c("aut", "trl"), comment = c(ORCID = "0000-0002-1503-6204")), person("Olivier", "Delaigue", role = c("aut", "cre"), comment = c(ORCID = "0000-0002-7668-8468"), email = "airGR@inrae.fr"), @@ -10,7 +10,7 @@ Authors@R: c( person("Charles", "Perrin", role = c("aut", "ths"), comment = c(ORCID = "0000-0001-8552-1881")), person("Claude", "Michel", role = c("aut", "ths")), person("Vazken", "Andréassian", role = c("ctb", "ths"), comment = c(ORCID = "0000-0001-7124-9303")), - person("François", "Bourgin", role = c("ctb"), comment = c(ORCID = "0000-0002-2820-7260", vignette = "'Parameter estimation' vignettes")), + person("François", "Bourgin", role = c("ctb"), comment = c(ORCID = "0000-0002-2820-7260")), person("Pierre", "Brigode", role = c("ctb"), comment = c(ORCID = "0000-0001-8257-0741")), person("Nicolas", "Le Moine", role = c("ctb")), person("Thibaut", "Mathevet", role = c("ctb"), comment = c(ORCID = "0000-0002-4142-4454")), diff --git a/NEWS.md b/NEWS.md index 6862db949f2e3498c450757325949d5cf850e99e..51cdf0480e7dc889511adb2f5fcfd21a9561beac 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,7 +4,12 @@ -### 1.4.3.91 Release Notes (2020-08-26) +### 1.4.4.2 Release Notes (2020-10-14) + +#### New features + +- <code>PE_Oudin()</code> now presents a <code>RunFortran</code> argument to run the code in Fortran or in R. The Fortran mode is the fastest. + #### Version control and issue tracking @@ -25,6 +30,8 @@ - Added output to <code>RunModel_GR2M()</code> function (Ps). +- <code>PE_Oudin()</code> can now run for several locations (i.e. several latitudes) in the Fortran mode (<code>RunFortran = TRUE</code>). In this case <code>Lat</code> must be of the same length as <code>Temp</code>. + #### Minor user-visible changes diff --git a/R/PE_Oudin.R b/R/PE_Oudin.R index 4113fc63b099aa1ad063a59a9d3c23f88347b74f..07b64ea1f3ae7d5b02a39cdba49398f5bbefd55c 100644 --- a/R/PE_Oudin.R +++ b/R/PE_Oudin.R @@ -1,6 +1,7 @@ PE_Oudin <- function(JD, Temp, Lat, LatUnit = c("rad", "deg"), - TimeStepIn = "daily", TimeStepOut = "daily") { + TimeStepIn = "daily", TimeStepOut = "daily", + RunFortran = FALSE) { ## ---------- check arguments @@ -18,22 +19,26 @@ PE_Oudin <- function(JD, Temp, stop("'Temp' must be of class 'numeric'") } if (length(JD) != length(Temp)) { - stop("'Temp' and 'LatUnit' must have the same length") + stop("'JD' and 'Temp' must have the same length") } - LatUnit <- match.arg(LatUnit, choices = c("rad", "deg")) - if (!inherits(Lat, "numeric") | length(Lat) != 1) { + + if (!RunFortran & (!inherits(Lat, "numeric") | length(Lat) != 1)) { stop("'Lat' must be a 'numeric' of length one") } - if (LatUnit[1L] == "rad" & ((Lat >= pi/2) | (Lat <= -pi/2))) { + if (RunFortran & (!inherits(Lat, "numeric") | (!length(Lat) %in% c(1, length(Temp))))) { + stop("'Lat' must be a 'numeric' of length one or of the same length as 'Temp'") + } + LatUnit <- match.arg(LatUnit, choices = c("rad", "deg")) + if (LatUnit[1L] == "rad" & (all(Lat >= pi/2) | all(Lat <= -pi/2))) { stop("'Lat' must be comprised between -pi/2 and +pi/2 degrees") } - if (LatUnit[1L] == "deg" & ((Lat >= 90) | (Lat <= -90))) { - stop("'Lat' must be comprised between -90 and +90 degrees") + if (LatUnit[1L] == "deg" & (all(Lat >= 90) | all(Lat <= -90))) { + stop("'Lat' must be comprised between -90 and +90 degrees") } - if (LatUnit[1L] == "rad") { + if (!RunFortran & LatUnit[1L] == "rad") { FI <- Lat } - if (LatUnit[1L] == "deg") { + if (!RunFortran & LatUnit[1L] == "deg") { FI <- Lat / (180 / pi) } if (any(JD < 0) | any(JD > 366)) { @@ -62,6 +67,31 @@ PE_Oudin <- function(JD, Temp, ## ---------- Oudin's formula + if (RunFortran) + { + if (LatUnit[1L] == "rad") { + Lat = Lat * 180 / pi + } + + LInputs = as.integer(length(Temp)) + + if (length(Lat) == 1) { + Lat = rep(Lat, LInputs) + } + + RESULTS <- .Fortran("frun_etp_oudin", PACKAGE="airGR", + ##inputs + LInputs = LInputs, + InputsLAT = as.double(Lat), + InputsTT = as.double(Temp), + InputsJJ = as.double(JD), + ##outputs + PE_Oudin_D = rep(as.double(-999.999), LInputs) + ) + PE_Oudin_D = RESULTS$PE_Oudin_D + + } else { + PE_Oudin_D <- rep(NA, length(Temp)) COSFI <- cos(FI) AFI <- abs(FI / 42) @@ -111,6 +141,7 @@ PE_Oudin <- function(JD, Temp, } + } ## ---------- disaggregate PE from daily to hourly diff --git a/man/PE_Oudin.Rd b/man/PE_Oudin.Rd index 031d8d1090315ba3ba79485665e784f61275eaaa..6760cbe1ec9b44a1a3c18228aa876cb18712f172 100644 --- a/man/PE_Oudin.Rd +++ b/man/PE_Oudin.Rd @@ -10,8 +10,9 @@ \usage{ -PE_Oudin(JD, Temp, Lat, LatUnit, - TimeStepIn = "daily", TimeStepOut = "daily") +PE_Oudin(JD, Temp, Lat, LatUnit = c("rad", "deg"), + TimeStepIn = "daily", TimeStepOut = "daily", + RunFortran = FALSE) ## deprectated function PEdaily_Oudin(JD, Temp, LatRad, Lat, LatUnit) @@ -25,13 +26,15 @@ PEdaily_Oudin(JD, Temp, LatRad, Lat, LatUnit) \item{LatRad}{(deprecated)[numeric] latitude of measurement for the temperature series [radians]. Please use \code{Lat} instead} -\item{Lat}{[numeric] latitude of measurement for the temperature series [radians or degrees]} +\item{Lat}{[numeric] latitude of measurement for the temperature series [radians or degrees]. Atomic vector, except if \code{RunFortran = TRUE}, it can be a vector of the same length as \code{Temp}} \item{LatUnit}{[character] latitude unit (default = \code{"rad"} or \code{"deg"})} \item{TimeStepIn}{[character] time step of inputs (e.g. \code{"daily"} or \code{"hourly"}, default = \code{"daily"})} \item{TimeStepOut}{[character] time step of outputs (e.g. \code{"daily"} or \code{"hourly"}, default = \code{"daily"})} + +\item{RunFortran}{[boolean] to run the code in the Fortran mode or in the R mode (default)} } @@ -70,7 +73,7 @@ PotEvap <- PE_Oudin(JD = as.POSIXlt(BasinObs$DatesR)$yday + 1, \author{ -Laurent Coron, Ludovic Oudin, Olivier Delaigue, Guillaume Thirel +Laurent Coron, Ludovic Oudin, Olivier Delaigue, Guillaume Thirel, François Bourgin } diff --git a/src/airGR.c b/src/airGR.c index 639675a4d94eed29148afc175092f2c1ef95edd7..b60f212f8e3f9ff51abd9f333b05bd4db03816a3 100644 --- a/src/airGR.c +++ b/src/airGR.c @@ -15,6 +15,7 @@ extern void F77_NAME(frun_gr5h)(int *, double *, double *, int *, double *, int extern void F77_NAME(frun_gr4j)(int *, double *, double *, int *, double *, int *, double *, int *, int *, double *, double *); extern void F77_NAME(frun_gr5j)(int *, double *, double *, int *, double *, int *, double *, int *, int *, double *, double *); extern void F77_NAME(frun_gr6j)(int *, double *, double *, int *, double *, int *, double *, int *, int *, double *, double *); +extern void F77_NAME(frun_etp_oudin)(int *, double *, double *, double *, double *); static const R_FortranMethodDef FortranEntries[] = { {"frun_cemaneige", (DL_FUNC) &F77_NAME(frun_cemaneige), 14}, @@ -25,6 +26,7 @@ static const R_FortranMethodDef FortranEntries[] = { {"frun_gr4j", (DL_FUNC) &F77_NAME(frun_gr4j), 11}, {"frun_gr5j", (DL_FUNC) &F77_NAME(frun_gr5j), 11}, {"frun_gr6j", (DL_FUNC) &F77_NAME(frun_gr6j), 11}, + {"frun_etp_oudin", (DL_FUNC) &F77_NAME(frun_etp_oudin), 5}, {NULL, NULL, 0} }; diff --git a/src/frun_ETP.f90 b/src/frun_ETP.f90 new file mode 100644 index 0000000000000000000000000000000000000000..c0c6f20ddf9dd5936066c43ab5b9ef34ae9e09d0 --- /dev/null +++ b/src/frun_ETP.f90 @@ -0,0 +1,124 @@ +!******************************************************************************* + SUBROUTINE frun_etp_oudin(LInputs,InputsLAT,InputsTT,InputsJJ,OutputsETP) +!******************************************************************************* + + !DEC$ ATTRIBUTES DLLEXPORT :: frun_etp_oudin + + Implicit None + + !! dummies + ! in + integer, intent(in) :: LInputs + doubleprecision, dimension(LInputs), intent(in) :: InputsLAT + doubleprecision, dimension(LInputs), intent(in) :: InputsTT + doubleprecision, dimension(LInputs), intent(in) :: InputsJJ + + ! out + doubleprecision, dimension(LInputs), intent(out) :: OutputsETP + + !! locals + integer :: k + real :: FI, tt, jj, ETPoud + + !-------------------------------------------------------------- + ! Time loop + !-------------------------------------------------------------- + DO k=1,LInputs + tt = InputsTT(k) + jj = InputsJJ(k) + FI = InputsLAT(k) / 57.296 + !model run on one time step + CALL ETP_OUDIN(FI,tt,jj,ETPoud) + !storage of outputs + OutputsETP(k) = ETPoud + ENDDO + + RETURN + + ENDSUBROUTINE + + +!******************************************************************************* + SUBROUTINE ETP_OUDIN(FI,DT,JD,DPE) +!******************************************************************************* +! This subroutine calculates daily potential evapotranspiration (DPE) +! using daily temperature and daily extra-atmospheric global radiation +! (that depends only on Julian day) +! +! The PE formula is is that described in: +! Oudin, L., Hervieu, F., Michel, C., Perrin, C., Andréassian, V., +! Anctil, F. and Loumagne, C., 2005. Which potential evapotranspiration +! input for a rainfall-runoff model? Part 2 - Towards a simple and +! efficient PE model for rainfall-runoff modelling. Journal of Hydrology +! 303(1-4), 290-306. +! +! For the calculation of extra-atmospheric global radiation, see Appendix C of +! the article by Morton, F.I., 1983. Operational estimates of areal +! evapotranspiration and their significance to the science and practice +! of hydrology. Journal of Hydrology 66 (1/4), 1-76. +! +!*************************************************************** +! Inputs: +! xLAT: Latitude in decimal degrees +! DT: Temperature in degree C +! JD: Julian day +! +! Output: +! DPE: Daily potential evapotranspiration in mm +!*************************************************************** + IMPLICIT NONE + + REAL :: xLAT, FI, COSFI, TETA, COSTETA, COSGZ, GZ, COSGZ2 + REAL :: SINGZ, COSOM, COSOM2, SINOM, COSPZ, OM, GE + REAL :: ETA, DPE, DT, JD, RD + +! DATA RD/57.296/ + +! Calculation of extra-atmospheric global radiation (Appendix C in Morton +! (1983), Eq. C-6 to C-11, p.60-61) +! Converts latitude in radians +! FI=xLAT/RD + COSFI=COS(FI) +! AFI=ABS(xLAT/42.) + +! TETA: Declination of the sun in radians + TETA=0.4093*SIN(JD/58.1-1.405) + COSTETA=COS(TETA) + COSGZ=MAX(0.001,COS(FI-TETA)) + +! GZ: Noon angular zenith distance of the sun + GZ=ACOS(COSGZ) + COSGZ2=COSGZ*COSGZ + + IF(COSGZ2.GE.1.)THEN + SINGZ=0. + ELSE + SINGZ=SQRT(1.-COSGZ2) + ENDIF + + COSOM=1.-COSGZ/COSFI/COSTETA + IF(COSOM.LT.-1.)COSOM=-1. + IF(COSOM.GT.1.)COSOM=1. + COSOM2=COSOM*COSOM + IF(COSOM2.GE.1.)THEN + SINOM=0. + ELSE + SINOM=SQRT(1.-COSOM2) + ENDIF + + OM=ACOS(COSOM) +! PZ: Average angular zenith distance of the sun + COSPZ=COSGZ+COSFI*COSTETA*(SINOM/OM-1.) + IF(COSPZ.LT.0.001)COSPZ=0.001 +! ETA: Radius vector of the sun + ETA=1.+COS(JD/58.1)/30. +! GE: extra-atmospheric global radiation + GE=446.*OM*COSPZ*ETA + +! Daily PE by Oudin et al. (2006) formula: + DPE=MAX(0.,GE*(DT+5.)/100./28.5) + + RETURN + + END SUBROUTINE ETP_OUDIN +!******************************************************************************* diff --git a/tests/testthat/test-evap.R b/tests/testthat/test-evap.R new file mode 100644 index 0000000000000000000000000000000000000000..aac8122d134e540315c3f4c986defad81de0d452 --- /dev/null +++ b/tests/testthat/test-evap.R @@ -0,0 +1,62 @@ +context("Test evaporation") + +comp_evap <- function(BasinObs, + Lat, LatUnit, + TimeStepIn = "daily", + TimeStepOut = "daily") { + PotEvap <- PE_Oudin(JD = as.POSIXlt(BasinObs$DatesR)$yday + 1, + Temp = BasinObs$T, + Lat = Lat, LatUnit = LatUnit, + TimeStepIn = TimeStepIn, TimeStepOut = TimeStepOut) + PotEvapFor <- PE_Oudin(JD = as.POSIXlt(BasinObs$DatesR)$yday + 1, + Temp = BasinObs$T, + Lat = Lat, LatUnit = LatUnit, + TimeStepIn = TimeStepIn, TimeStepOut = TimeStepOut, + RunFortran = TRUE) + all(range(PotEvap - PotEvapFor) < 0.000001) +} + +test_that("PE_Oudin works", { + skip_on_cran() + rm(list = ls()) + + data(L0123001); BasinObs_L0123001 <- BasinObs + data(L0123002); BasinObs_L0123002 <- BasinObs + + expect_true(comp_evap(BasinObs = BasinObs_L0123001, + Lat = 0.8, LatUnit = "rad", + TimeStepIn = "daily", TimeStepOut = "daily")) + expect_true(comp_evap(BasinObs = BasinObs_L0123001, + Lat = 0.8, LatUnit = "rad", + TimeStepIn = "daily", TimeStepOut = "hourly")) + expect_true(comp_evap(BasinObs = BasinObs_L0123002, + Lat = 0.9, LatUnit = "rad", + TimeStepIn = "daily", TimeStepOut = "daily")) + expect_true(comp_evap(BasinObs = BasinObs_L0123002, + Lat = 0.9, LatUnit = "rad", + TimeStepIn = "daily", TimeStepOut = "hourly")) + + ## check with several catchments using different values for Lat + + ## one by one + PotEvapFor1 <- PE_Oudin(JD = as.POSIXlt(BasinObs_L0123001$DatesR)$yday + 1, + Temp = BasinObs_L0123001$T, + Lat = 0.8, LatUnit = "rad", + RunFortran = TRUE) + PotEvapFor2 <- PE_Oudin(JD = as.POSIXlt(BasinObs_L0123002$DatesR)$yday + 1, + Temp = BasinObs_L0123002$T, + Lat = 0.9, LatUnit = "rad", + RunFortran = TRUE) + + ## all in one + BasinObs_L0123001$Lat <- 0.8 + BasinObs_L0123002$Lat <- 0.9 + BasinObs <- rbind(BasinObs_L0123001, BasinObs_L0123002) + PotEvapFor <- PE_Oudin(JD = as.POSIXlt(BasinObs$DatesR)$yday + 1, + Temp = BasinObs$T, + Lat = BasinObs$Lat, LatUnit = "rad", + RunFortran = TRUE) + + expect_equal(PotEvapFor, c(PotEvapFor1, PotEvapFor2)) + +})