Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tidy up leap year implementation, and other date-related cleanup #1623

Merged
merged 18 commits into from
Sep 5, 2017

Conversation

ashiklom
Copy link
Member

@ashiklom ashiklom commented Sep 1, 2017

Description

New function PEcAn.utils::days_in_year(year) calculates the days in a year given a year (or vector of years). I used this function to replace many occurrences of ifelse(leap_year(year), 366, 355) many places in the code. I also simplified many statements related to leap year if-else clauses, where it was simple enough to do so.

Also, new function PEcAn.data.atmsphere::eccentricity_obliquity(doy) does the appropriate math. This was refactored out of several places that had this fairly complex equation duplicated.

Also, all of the files I touched have had trailing whitespace removed (courtesy of RStudio), which extends the review diff, but is good coding practice.

Motivation and Context

See issue #801.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)

Checklist:

  • My change requires a change to the documentation.
  • I have updated the CHANGELOG.md.
  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

New function `PEcAn.utils::days_in_year(year)` calculates the days in a year given a year (or vector of years). I used this function to replace many occurrences of `ifelse(leap_year(year), 366, 355)` many places in the code. I also simplified many statements related to leap year if-else clauses, where it was simple enough to do so.

Also, new function `PEcAn.data.atmsphere::eccentricity_obliquity(doy)` does the appropriate math. This was refactored out of several places that had this fairly complex equation duplicated.

Also, all of the files I touched have had trailing whitespace removed (courtesy of RStudio), which extends the review diff, but is good coding practice.
For completeness, even though it doesn't actually do anything yet.
f <- pi/180 * (279.5 + 0.9856 * doy)
et <- (-104.7 * sin(f) + 596.2 * sin(2 * f) + 4.3 * sin(4 * f) - 429.3 *
cos(f) - 2 * cos(2 * f) + 19.3 * cos(3 * f)) / 3600 # equation of time -> eccentricity and obliquity
et <- PEcAn.data.atmosphere::eccentricity_obliquity(doy)
Copy link
Member

Choose a reason for hiding this comment

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

I think it's great to encapsulate this, but you could probably go even further and encapsulate the whole solar geometry calculation of solar zenith angle as well (i.e. everything up to cosz below)

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I was thinking that too. I went through it quickly and pulled this out because it was really obvious and I saw it in a few places. But yeah, I'll go ahead and do that, so wait to merge this until I do.

@mdietze
Copy link
Member

mdietze commented Sep 1, 2017

@ashiklom you've got a ROxygen fail on models/jules/man/detect.timestep.Rd

dt <- (366 * 24 * 60 * 60) / length(sec), # leap year
dt <- (365 * 24 * 60 * 60) / length(sec)) # non-leap year
diy <- PEcAn.utils::days_in_year(year)
dt <- diy * 24 * 60 * 60 / length(sec)
Copy link
Member

Choose a reason for hiding this comment

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

wouldn't the convention be to use

udunits::ud.convert(diy, 'days', 'seconds') / length(sec)

dtmp <- rep(1:366, each = 86400 / dt)
}
diy <- PEcAn.utils::days_in_year(y)
ytmp <- rep(y, diy * 86400 / dt)
Copy link
Member

Choose a reason for hiding this comment

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

another case where seconds / day is hardcoded. Better to use ud.convert?

@@ -146,7 +145,7 @@ met2model.ED2 <- function(in.path, in.prefix, outfolder, start_date, end_date, l
if (useCO2) {
CO2 <- c(rep(CO2[1], toff), CO2)[1:slen]
}

## build time variables (year, month, day of year)
skip <- FALSE
nyr <- floor(length(sec) / 86400 / 365 * dt)
Copy link
Member

Choose a reason for hiding this comment

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

looks like a kludge. Could ud.convert work here too?



diy <- PEcAn.utils::days_in_year(year)
dt <- diy * 24 * 60 * 60 / length(sec)
Copy link
Member

Choose a reason for hiding this comment

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

use ud.convert? This pattern seems to be repeated a lot. Maybe we need another function seconds_in_year?

cosz <- sin(lat * pi/180) * sin(dec) + cos(lat * pi/180) * cos(dec) * cos(h)
cosz[cosz < 0] <- 0

cosz <- PEcAn.data.atmosphere::solar_angle(doy, lat, lon, dt)
Copy link
Member

Choose a reason for hiding this comment

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

does this function return solar_angle, or cos(zenith angle)? I would expect that solar_angle to return zenith_angle, and perhaps function should be named cos_solar_zenith_angle???

@@ -628,7 +628,8 @@ detect.timestep <- function(met.dir,met.regexp,start_date){
tlen <- grep("time =", met.header)
if (length(tlen) > 0) {
tlen <- as.numeric(gsub(pattern = "[^[:digit:]]", "", met.header[tlen]))
dt <- 86400 / round(tlen/(365 + lubridate::leap_year(as.Date(start_date))))
diy <- PEcAn.utils::days_in_year(lubridate::year(as.Date(start_date)))
dt <- 86400 / round(tlen/(diy))
Copy link
Member

Choose a reason for hiding this comment

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

another repeated could this be wrapped in a function called dt?

VPD <- VPD * 0.01 # convert to Pascal

Copy link
Member

Choose a reason for hiding this comment

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

these transformations should also use ud.convert

366 * 24 * 60 * 60 / length(sec), # leap year
365 * 24 * 60 * 60 / length(sec)) # non-leap year

dt <- diy * 24 * 60 * 60 / length(sec)
Copy link
Member

Choose a reason for hiding this comment

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

!!!!!

@@ -43,33 +43,33 @@ calculate.nee.L <- function(yeardoytime, model.i.nee, observed.flux, be, bu) {
get.da.data <- function(out.dir, ameriflux.dir, years, be, bu, ensemble.size = 199) {
ensemble.size <- 500
Copy link
Member

Choose a reason for hiding this comment

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

is this hard-coded value of ensemble size a bug?

ntime <- ifelse(year %% 4 == 0, 2923, 2919) # should use lubridate::leap_year?

ntime <- ifelse(lubridate::leap_year(year), 2923, 2919) ## ANS: Where do these numbers come from?
Copy link
Member

Choose a reason for hiding this comment

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

these numbers are the number of 3 hour intervals in a year -1 (365*8=2920)

Copy link
Member

Choose a reason for hiding this comment

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

perhaps can be replaced by days_in_year(year) * 8 - 1?

#' @param doy Day of year
#' @param lat Latitude
#' @param lon Longitude
#' @param dt Timestep
Copy link
Member

Choose a reason for hiding this comment

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

could be better documented.

  • What is the return value?
  • cite a canonical source of the equations used
  • add description

Should the name be cos_solar_zenith_angle to be more explicit?

#' @author Alexey Shiklomanov
#' @param doy Day of year
#' @export
eccentricity_obliquity <- function(doy) {
Copy link
Member

Choose a reason for hiding this comment

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

ibid

eccentricity *
(cos(declination) *
cos(deg2rad(LAT)) *
radiation = 1367.0 *
Copy link
Member

Choose a reason for hiding this comment

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

magic number !!!also uses = instead of <-

Copy link
Member Author

Choose a reason for hiding this comment

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

This is an old script (not a package function definition) that hasn't been touched in a while, so I'm just gonna leave it. There's a lot more that's wrong with it, which I don't feel like spending time fixing.

Copy link
Member

@dlebauer dlebauer left a comment

Choose a reason for hiding this comment

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

Looks like a good start

I made a few comments - it seems that there is an opportunity to clean up some more magic numbers as seconds/ day is repeatedly calculated or given as a magic number. dt <- diy * 24 * 60 * 60 / length(sec)

I found a few bits of code that you didn't explicitly touch but are worth looking at.

Also not sure what the current thinking on roll-your-own vs. use existing packages, but there is already the insol package that calculates solar zenith angle and potential radiation

Use it in all the `met2model` code instead of hard-coded math.
Also, Rstudio removed trailing whitespace from a bunch of files.
Also, some more whitespace fixes by RStudio.
@ashiklom
Copy link
Member Author

ashiklom commented Sep 5, 2017

@dlebauer I addressed many of your comments, but to address all of the cases where we have had-coded date-time conversions would take a while. I definitely agree that all of our met processing and met2model code could use some serious cleanup and refactoring, but I don't have the time to sift through every line to figure out what the conversions are (especially since I wrote none of this original code, so it's often hard for me to divine exactly what's going on). I think this PR makes decent progress on a lot of the required cleanup, and it's worth merging now and continuing the process in later pull requests. If you or @mdietze disagree, you can request additional changes to the PR, but it'll be a little while before I get to them since I have a lot of other stuff to catch up on.


## Format/convert inputs
ppfd <- tapply(PPFD, doy, mean, na.rm = TRUE) # Find the mean for the day
tair <- udunits2::ud.convert(tapply(Tair, doy, mean, na.rm = TRUE), "kelvin", "celsius") # Convert Kelvin to Celcius
vpd <- udunits2::ud.convert(tapply(VPD, doy, mean, na.rm = TRUE), "Pa", "kPa") # pascal to kila pascal
precip <- tapply(Precip, doy, sum, na.rm = TRUE) # Sum to daily precipitation
co2 <- tapply(CO2, doy, mean) # need daily average, so sum up day
co2 <- co2 / 1e+06 # convert to ppm
co2 <- co2 / 1e+06 # convert to ppm. ANS: Convert from what? Mole-fraction to ppm is multiplying by 1e6, not dividing
Copy link
Member

Choose a reason for hiding this comment

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

Is this an error that is currently causing invalid calculations? If so, who can resolve it?

Copy link
Member

Choose a reason for hiding this comment

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

@tonygardella wrote this coupler, so I think he can check and resolve this.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'll fix it!

@mdietze mdietze merged commit 009e165 into PecanProject:develop Sep 5, 2017
@ashiklom ashiklom deleted the date-cleanup branch October 3, 2017 21:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants