From 83bf349f09e3889bc65a38baf1f454d56aa23abd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fran=C3=A7ois=20Bourgin?= <francois.bourgin@inrae.fr>
Date: Fri, 17 Apr 2020 18:10:13 +0200
Subject: [PATCH] NEW: ETP Oudin daily in Fortran

---
 R/PEdaily_Oudin.R | 122 +++++++++++++++++++++++++-------------------
 src/airGR.c       |   2 +
 src/frun_ETP.f90  | 126 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 199 insertions(+), 51 deletions(-)
 create mode 100644 src/frun_ETP.f90

diff --git a/R/PEdaily_Oudin.R b/R/PEdaily_Oudin.R
index b71ac999..b8fcef19 100644
--- a/R/PEdaily_Oudin.R
+++ b/R/PEdaily_Oudin.R
@@ -49,67 +49,87 @@ PEdaily_Oudin <- function(JD, Temp, LatRad, Lat, LatUnit = c("rad", "deg")) {
   
   
   ## ---------- Oudin's formula
-  
-  PE_Oudin_D <- rep(NA, length(Temp))
-  COSFI <- cos(FI)
-  AFI <- abs(FI / 42) 
-  
-  for (k in seq_along(Temp)) {
-    
-    TETA <- 0.4093 * sin(JD[k] / 58.1 - 1.405)
-    COSTETA <- cos(TETA)
-    COSGZ <- max(0.001, cos(FI - TETA))
-    GZ <- acos(COSGZ)
-    COSOM <- 1 - COSGZ / COSFI / COSTETA
-    
-    if (COSOM < -1) {
-      COSOM <- -1
-    }
-    if (COSOM > 1) {
-      COSOM <-  1
+  run_fortran = T
+  if (run_fortran)
+  {
+    if (LatUnit[1L] == "rad") {
+      Lat = Lat * 180 / pi
     }
+    LInputs = as.integer(length(Temp))
+
+    RESULTS <- .Fortran("frun_etp_oudin", PACKAGE="airGR",
+                        ##inputs
+                        LInputs = LInputs,
+                        xLAT = as.double(Lat),
+                        InputsTT = as.double(Temp),
+                        InputsJJ = as.double(JD),
+                        ##outputs                                                               
+                        PE_Oudin_D = rep(as.double(-999.999), LInputs)
+    )
+  } else {
     
-    COSOM2 <- COSOM * COSOM
+    PE_Oudin_D <- rep(NA, length(Temp))
+    COSFI <- cos(FI)
+    AFI <- abs(FI / 42) 
     
-    if (COSOM2 >= 1) {
-      SINOM <- 0
-    } else {
-      SINOM <- sqrt(1 - COSOM2)
+    for (k in seq_along(Temp)) {
+      
+      TETA <- 0.4093 * sin(JD[k] / 58.1 - 1.405)
+      COSTETA <- cos(TETA)
+      COSGZ <- max(0.001, cos(FI - TETA))
+      GZ <- acos(COSGZ)
+      COSOM <- 1 - COSGZ / COSFI / COSTETA
+      
+      if (COSOM < -1) {
+        COSOM <- -1
+      }
+      if (COSOM > 1) {
+        COSOM <-  1
+      }
+      
+      COSOM2 <- COSOM * COSOM
+      
+      if (COSOM2 >= 1) {
+        SINOM <- 0
+      } else {
+        SINOM <- sqrt(1 - COSOM2)
+      }
+      
+      OM <- acos(COSOM)
+      COSPZ <- COSGZ + COSFI * COSTETA * (SINOM/OM - 1)
+      
+      if (COSPZ < 0.001) {
+        COSPZ <- 0.001
+      }
+      
+      ETA <- 1 + cos(JD[k] / 58.1) / 30
+      GE <- 446 * OM * COSPZ * ETA
+      
+      if (is.na(Temp[k])) {
+        PE_Oudin_D[k] <- NA
+      } else {
+        if (Temp[k] >= -5.0) {
+          PE_Oudin_D[k] <- GE * (Temp[k] + 5) / 100 / 28.5
+        } else {
+          PE_Oudin_D[k] <- 0
+        }
+      }
+      
     }
     
-    OM <- acos(COSOM)
-    COSPZ <- COSGZ + COSFI * COSTETA * (SINOM/OM - 1)
-    
-    if (COSPZ < 0.001) {
-      COSPZ <- 0.001
-    }
-    
-    ETA <- 1 + cos(JD[k] / 58.1) / 30
-    GE <- 446 * OM * COSPZ * ETA
-    
-    if (is.na(Temp[k])) {
-      PE_Oudin_D[k] <- NA
-    } else {
-    if (Temp[k] >= -5.0) {
-      PE_Oudin_D[k] <- GE * (Temp[k] + 5) / 100 / 28.5
-    } else {
-      PE_Oudin_D[k] <- 0
+    if (any(is.na(Temp))) {
+      if (any(is.na(PE_Oudin_D))) {
+        warning("'Temp' time series, and therefore the returned 'PE' time series, contain missing value(s)")
+      } else {
+        warning("'Temp' time series contains missing value(s)")
+      }
     }
+    if (!any(is.na(Temp)) & any(is.na(PE_Oudin_D))) {
+      warning("returned 'PE' time series contains missing value(s)")
     }
     
   }
   
-  if (any(is.na(Temp))) {
-    if (any(is.na(PE_Oudin_D))) {
-      warning("'Temp' time series, and therefore the returned 'PE' time series, contain missing value(s)")
-    } else {
-      warning("'Temp' time series contains missing value(s)")
-    }
-  }
-  if (!any(is.na(Temp)) & any(is.na(PE_Oudin_D))) {
-    warning("returned 'PE' time series contains missing value(s)")
-  }
-  
   return(PE_Oudin_D)
   
 }
diff --git a/src/airGR.c b/src/airGR.c
index 639675a4..b60f212f 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 00000000..5bde3e9c
--- /dev/null
+++ b/src/frun_ETP.f90
@@ -0,0 +1,126 @@
+!*******************************************************************************
+      SUBROUTINE frun_etp_oudin(LInputs,LAT,InputsTT,InputsJJ,OutputsETP)
+!*******************************************************************************
+
+      !DEC$ ATTRIBUTES DLLEXPORT :: frun_etp_oudin
+
+      Implicit None
+
+      !! dummies
+      ! in
+      integer, intent(in) :: LInputs
+      doubleprecision, intent(in) :: LAT
+      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
+      
+      FI = LAT / 57.296
+      
+      !--------------------------------------------------------------
+      ! Time loop
+      !--------------------------------------------------------------
+      DO k=1,LInputs
+        tt = InputsTT(k)
+        jj = InputsJJ(k)
+        ETPoud = -99.9
+        !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
+!*******************************************************************************
-- 
GitLab