-
Notifications
You must be signed in to change notification settings - Fork 313
fix(flux.uncertainty): prevent crash when positive-flux model is not fitted #4011
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
Open
anshul23102
wants to merge
4
commits into
PecanProject:develop
Choose a base branch
from
anshul23102:fix/flux-uncertainty-undefined-mp
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+92
−10
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
ae95d94
fix(flux.uncertainty): prevent crash when positive-flux model is not …
anshul23102 bb34964
Merge branch 'develop' into fix/flux-uncertainty-undefined-mp
anshul23102 cf66daa
test(flux.uncertainty): add testthat tests for the undefined-mp crash…
anshul23102 db6d91f
test(uncertainty): fix third flux.uncertainty test scenario
anshul23102 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
78 changes: 78 additions & 0 deletions
78
modules/uncertainty/tests/testthat/test.flux_uncertainty.R
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| ## Tests for flux.uncertainty() | ||
| ## | ||
| ## The bug fixed in this PR: | ||
| ## When all flux measurements are negative (all bins fall below zero), | ||
| ## the positive-flux linear model `mp` is never assigned. The original | ||
| ## code unconditionally referenced `mp` in the else-branch for slopeN, | ||
| ## causing an "object 'mp' not found" error at runtime. | ||
| ## | ||
| ## Fix: the else-branch is now `else if (exists("mp", inherits = FALSE))` | ||
| ## so slopeN falls back to the positive model only when it was actually fit. | ||
|
|
||
| ## Helper: create paired flux measurements where even-indexed values | ||
| ## (which become the "magnitude" in flux.uncertainty) span a requested range. | ||
| make_flux_series <- function(range_lo, range_hi, n_pairs = 80, sd_noise = 0.2, seed = 1) { | ||
| set.seed(seed) | ||
| base <- seq(range_lo, range_hi, length.out = n_pairs) | ||
| # interleave: odd positions = one reading, even positions = paired reading | ||
| meas <- as.vector(rbind( | ||
| base + stats::rnorm(n_pairs, sd = sd_noise), | ||
| base + stats::rnorm(n_pairs, sd = sd_noise) | ||
| )) | ||
| QC <- rep(0L, length(meas)) | ||
| list(measurement = meas, QC = QC) | ||
| } | ||
|
|
||
| test_that("flux.uncertainty returns cleanly when all fluxes are negative (mp never assigned)", { | ||
| d <- make_flux_series(range_lo = -20, range_hi = -2) | ||
|
|
||
| # Before the fix this would throw "object 'mp' not found" | ||
| result <- suppressMessages( | ||
| capture.output( | ||
| val <- flux.uncertainty(d$measurement, QC = d$QC, bin.num = 5, minBin = 3), | ||
| type = "output" | ||
| ) | ||
| ) | ||
|
|
||
| expect_type(val, "list") | ||
| expect_true(all(c("mag", "err", "bias", "n", "intercept") %in% names(val)), | ||
| label = "required list elements present") | ||
| # No positive bins -> slopeP should not be set | ||
| expect_false("slopeP" %in% names(val), | ||
| label = "slopeP absent when no positive-flux bins exist") | ||
| }) | ||
|
|
||
| test_that("flux.uncertainty fits both slopes for mixed positive/negative flux", { | ||
| d <- make_flux_series(range_lo = -15, range_hi = 15) | ||
|
|
||
| capture.output( | ||
| val <- flux.uncertainty(d$measurement, QC = d$QC, bin.num = 8, minBin = 3), | ||
| type = "output" | ||
| ) | ||
|
|
||
| expect_type(val, "list") | ||
| expect_true("slopeP" %in% names(val), label = "slopeP present for mixed data") | ||
| expect_true("slopeN" %in% names(val), label = "slopeN present for mixed data") | ||
| expect_true(is.numeric(val$slopeP) && length(val$slopeP) == 1) | ||
| expect_true(is.numeric(val$slopeN) && length(val$slopeN) == 1) | ||
| }) | ||
|
|
||
| test_that("flux.uncertainty uses positive-slope fallback for slopeN when neg bins all NA", { | ||
| # Data spans a zero crossing but stays almost entirely positive. | ||
| # With bin.num=8 and minBin=5 the tiny negative portion falls below the | ||
| # minimum bin size, so errBin[neg] is NA for every negative bin. | ||
| # The else-if branch therefore assigns slopeN = mp$coefficients[1], | ||
| # making slopeN equal to slopeP. | ||
| d <- make_flux_series(range_lo = -0.5, range_hi = 20, seed = 3) | ||
|
|
||
| capture.output( | ||
| val <- flux.uncertainty(d$measurement, QC = d$QC, bin.num = 8, minBin = 5), | ||
| type = "output" | ||
| ) | ||
|
|
||
| expect_type(val, "list") | ||
| expect_true("slopeP" %in% names(val), label = "slopeP present when positive bins have data") | ||
| expect_true("slopeN" %in% names(val), label = "slopeN set via mp fallback when neg bins empty") | ||
| expect_equal(val$slopeN, val$slopeP, | ||
| label = "slopeN equals slopeP when neg bins unavailable and mp is the fallback") | ||
| }) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated code is going to leave slopeN undefined if the new if is false. Doing so silently is going to cause issues downstream. Seems like there should be an additional else that sets slopeN to something, as well as a similar set of cases for the earlier slopeP. One option would be a sensible default (e.g., mean value from analyzing many site-years of data), another would be a NA or something like that. If we do set the parameter to NA, it would be good to check what existing PEcAn code calls this function to see what additional checks would be useful there.