-
Notifications
You must be signed in to change notification settings - Fork 282
Replace manual unit conversions with ud_convert() for consistency across models #3719
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
base: develop
Are you sure you want to change the base?
Changes from all commits
5599e09
2fc0106
c425ca7
747277a
c29e56f
a2d79d5
8e2b0a4
aa8f2d4
e6ad156
0c2bb74
1aeddf7
570b899
9970af2
476de3d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| # Test script to validate unit conversion changes | ||
| # This verifies that ud_convert works with the new unit strings | ||
|
|
||
| library(PEcAn.utils) | ||
|
|
||
| cat("Testing unit string validity with UDUNITS2...\n\n") | ||
|
|
||
| # Test cases from the modified files | ||
| test_conversions <- list( | ||
| list(value = 100, u1 = "g/m2", u2 = "kg/m2", expected = 0.1, desc = "DALEC/SIPNET pool: g/m2 to kg/m2"), | ||
| list(value = 86400, u1 = "g/m2/d", u2 = "kg/m2/s", expected = 0.001, desc = "DALEC/SIPNET flux: g/m2/d to kg/m2/s"), | ||
| list(value = 10, u1 = "Mg/ha", u2 = "kg/m2", expected = 1, desc = "GDAY: Mg/ha to kg/m2"), | ||
| list(value = 1000, u1 = "J/mol", u2 = "kJ/mol", expected = 1, desc = "FATES: J/mol to kJ/mol") | ||
| ) | ||
|
|
||
| results <- list() | ||
| for (i in seq_along(test_conversions)) { | ||
| test <- test_conversions[[i]] | ||
| tryCatch({ | ||
| result <- ud_convert(test$value, test$u1, test$u2) | ||
| passed <- abs(result - test$expected) < 1e-10 | ||
| results[[i]] <- list( | ||
| desc = test$desc, | ||
| passed = passed, | ||
| result = result, | ||
| expected = test$expected | ||
| ) | ||
| cat(sprintf("[%s] %s\n", if(passed) "PASS" else "FAIL", test$desc)) | ||
| if (!passed) { | ||
| cat(sprintf(" Expected: %f, Got: %f\n", test$expected, result)) | ||
| } | ||
| }, error = function(e) { | ||
| results[[i]] <<- list(desc = test$desc, passed = FALSE, error = e$message) | ||
| cat(sprintf("[FAIL] %s\n", test$desc)) | ||
| cat(sprintf(" Error: %s\n", e$message)) | ||
| }) | ||
| } | ||
|
|
||
| cat("\n") | ||
| passed_count <- sum(sapply(results, function(x) x$passed)) | ||
| total_count <- length(results) | ||
| cat(sprintf("Summary: %d/%d tests passed\n", passed_count, total_count)) | ||
|
|
||
| if (passed_count < total_count) { | ||
| cat("\nFAILED TESTS DETECTED - These must be fixed before proceeding\n") | ||
| quit(status = 1) | ||
| } else { | ||
| cat("\nAll unit strings are valid!\n") | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| # Regression tests for unit conversion refactoring | ||
| # Run with: testthat::test_dir("tests/testthat", filter = "test_model_conversions") | ||
|
|
||
| source("test_model_conversions.R") | ||
|
Comment on lines
+1
to
+4
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. delete this file -- test_model_conversions.R belongs in base/utils/tests/testthat, which already has an existing testthat.R |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
|
|
||
| # These tests validate the refactoring to use PEcAn.utils::ud_convert() | ||
| # for consistent unit conversions across SIPNET, DALEC, GDAY, and FATES models. | ||
|
|
||
| test_that("UDUNITS2 recognizes new unit strings", { | ||
|
|
||
| # Pool conversions (mass only) | ||
| expect_equal(ud_convert(100, "g/m2", "kg/m2"), 0.1) | ||
| expect_equal(ud_convert(1000, "g/m2", "kg/m2"), 1.0) | ||
|
Comment on lines
+7
to
+9
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do not think we need to check the same unit conversion at multiple values -- Most of the actual conversion machinery is provided by the external
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or in less words: "Let's only check each unit pair once" |
||
|
|
||
| # Flux conversions (mass and time) | ||
| expect_equal(ud_convert(86400, "g/m2/d", "kg/m2/s"), 0.001, tolerance = 1e-10) | ||
|
|
||
| # Large-scale conversions (GDAY) | ||
| expect_equal(ud_convert(10, "Mg/ha", "kg/m2"), 1.0) | ||
|
|
||
| # Energy conversions (FATES) | ||
| expect_equal(ud_convert(1000, "J/mol", "kJ/mol"), 1.0) | ||
| }) | ||
|
|
||
| test_that("SIPNET flux conversions produce realistic values", { | ||
|
|
||
| # GPP: ~5 g C/m2/d --> kg/m2/s | ||
| expect_equal(ud_convert(5, "g/m2/d", "kg/m2/s"), 5.79e-8) | ||
|
|
||
| # Pool: 1000 g C/m2 → 1 kg/m2 | ||
| wood_result <- ud_convert(1000, "g/m2", "kg/m2") | ||
| expect_equal(wood_result, 1.0) | ||
| }) | ||
|
|
||
| test_that("DALEC flux conversions are mathematically sound", { | ||
| library(PEcAn.utils) | ||
|
|
||
| # Autotrophic respiration: 5 g C/m2/d → kg/m2/s | ||
| ar_result <- ud_convert(5, "g/m2/d", "kg/m2/s") | ||
| expect_true(ar_result > 0) | ||
| expect_true(ar_result < 5) | ||
|
|
||
| # Leaf carbon: 200 g C/m2 → 0.2 kg/m2 | ||
| leaf_result <- ud_convert(200, "g/m2", "kg/m2") | ||
| expect_equal(leaf_result, 0.2) | ||
| }) | ||
|
|
||
| test_that("GDAY conversions from Mg/ha to kg/m2 work correctly", { | ||
| library(PEcAn.utils) | ||
|
|
||
| # 5 Mg/ha = 0.5 kg/m2 | ||
| stem_result <- ud_convert(5, "Mg/ha", "kg/m2") | ||
| expect_equal(stem_result, 0.5) | ||
|
|
||
| # 50 Mg/ha = 5 kg/m2 | ||
| soil_result <- ud_convert(50, "Mg/ha", "kg/m2") | ||
| expect_equal(soil_result, 5.0) | ||
| }) | ||
|
|
||
| test_that("No 'C' prefix in unit strings (UDUNITS2 requirement)", { | ||
| library(PEcAn.utils) | ||
|
|
||
| # These should NOT work (they include 'C' for carbon) | ||
| expect_error(ud_convert(100, "gC/m2", "kgC/m2")) | ||
| expect_error(ud_convert(86400, "gC/m2/d", "kgC/m2/s")) | ||
|
Comment on lines
+60
to
+61
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like that you're taking a problem seen during development and trying to turn it into a test! But I don't think these expectations are useful to us -- they will only fail if udunits does learn how to parse It's possible that |
||
|
|
||
| # But these (without 'C') should work | ||
| expect_equal(ud_convert(100, "g/m2", "kg/m2"), 0.1) | ||
| expect_equal(ud_convert(86400, "g/m2/d", "kg/m2/s"), 0.001, tolerance = 1e-10) | ||
|
Comment on lines
+63
to
+65
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. already checked above |
||
| }) | ||
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.
Thanks for including tests! Unit tests like this should be placed in the
tests/testthatfolder of the appropriate package and not to the top-leveltestsfolder, which is used for whole-system test material (and is due for a reorg/cleanout, but that's a separate issue). For tests ofud_convertitself, the appropriate package isPEcAn.utilsso they should be added to the existingbase/utils/tests/testthat/test-ud_convert.R.I also request using standard testthat expectations rather than rolling your own comparison machinery -- this entire script is basically equivalent to