-
Notifications
You must be signed in to change notification settings - Fork 2
Downscaling mixed systems #5
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
Merged
Merged
Changes from all commits
Commits
Show all changes
73 commits
Select commit
Hold shift + click to select a range
e07e3a8
added exploration demo of different approaches to aggregating multipl…
dlebauer 5c8d46e
add points to plot
dlebauer 1560630
update config - change location of model outputs, deprecate use of .f…
dlebauer ddbde37
Add nearest‐polygon matching for unmatched sites
dlebauer 696a933
add note in docs that workflows will be split into site selection and…
dlebauer ff23d5e
removed duplicate chunk from 009
dlebauer 15e6316
Refactor SIPNET output extraction script for clarity and efficiency; …
dlebauer 12f4a43
clarify documentation; disable renv auto activation in .Rprofile
dlebauer d0cd427
major refactoring of mixed_system_prototype.qmd
dlebauer 68ef946
Added 031_agggregate_sipnet_output.R to handle PFT mixture logic
dlebauer 258ef31
Update docs/mixed_system_prototype.qmd
dlebauer 07ea167
add warning that we need new method for flux variables
dlebauer 2758e83
addressing pr suggestions
dlebauer 7e4c034
small wording changes to explain figs
dlebauer da406d5
few more minor changes
dlebauer 1101916
few more tweaks
dlebauer c517598
add mixed aggregation helper function
dlebauer 36a3054
standardize naming of ensemble_data object
dlebauer 412ffd5
in the middle of refactoring to handle multiple PFTs in the downscaling
dlebauer 08e1532
Rename downscaling scripts, split downscale+aggregate into separate s…
dlebauer 295ec2e
Set up quarto publish gh-pages: add _quarto.yml, update README, etc
dlebauer 7de1418
Add ggsave_optimized function and update plot saving in scripts
dlebauer 61afd26
rename, vectorize, and test combine_value --> combine_mixed_crops.
dlebauer b576d1e
Refactor county-level plotting to include delta calculations for car…
dlebauer 176e200
downscaling script: add mixed scenario calculations for carbon densit…
dlebauer f5a8bf2
Add testthat helper for logging configuration and sourcing R functions
dlebauer 7e4c5e6
Add references section and update workflow documentation title and fo…
dlebauer 2fd85c9
- Separate out `variable_importance.qmd` and `deisng_points_analysis.…
dlebauer a2e81d8
Update document formats to non-self-contained and adjust references i…
dlebauer d89297e
Remove unused masking threshold variable from configuration
dlebauer 57c2f90
Add county comparison sections and update workflow documentation, rem…
dlebauer 47c0aa1
Update file paths in mixed_system_prototype.qmd and refine variable i…
dlebauer 9e3bc0b
Change default mode production=true, refine README and workflow docum…
dlebauer f3a5a9d
fix link
dlebauer 3b0b24e
fix link
dlebauer 04065d9
Move sidebar to left and add gh link; add alert callout to README
dlebauer a44d5a0
Apply suggestion from @infotroph
dlebauer ec9e30b
Apply suggestion from @dlebauer
dlebauer 0627842
Apply suggestion from @infotroph
dlebauer 169a229
Apply suggestion from @infotroph
dlebauer 422d3f0
Apply suggestion from @dlebauer
dlebauer e63313c
Update 000-config.R
dlebauer ea84ef9
Update combine_mixed_crops.R
dlebauer 9597158
track compressed archive path; log pecan_archive_tgz
dlebauer 6fbedb5
add configural scales, high res export
dlebauer d8c57e5
update ggsave_optimized to also support high res * color-blind friend…
dlebauer 0bdf3a4
document uncompressing model outputs
dlebauer 9177cc3
Merge remote-tracking branch 'origin/mixed_downscaling' into mixed_do…
dlebauer fffdd70
clarify that combine_mixed_crops works on pools and cumulative fluxes…
dlebauer e1dcaed
dropped intermediate list and now call vec_recycle_common with named …
dlebauer 485331b
Apply suggestion from @dlebauer
dlebauer da369e4
Apply suggestion from @divine7022
dlebauer c13eb17
clarify docs related to matching first on ids then location; correct…
dlebauer 27d9895
check that target and reference dfs don't contain duplicate site_ids …
dlebauer 503e996
remove redundant params (just use inherited params)
dlebauer ceee65b
check that n>1 and sd >0 for ensemble stats
dlebauer 5b882dc
return numeric
dlebauer 4e67335
Apply suggestion from @infotroph
dlebauer f6197b0
enforce projected CRS for California Albers, prefer ALE plots; only a…
dlebauer cc282b1
Merge remote-tracking branch 'origin/mixed_downscaling' into mixed_do…
dlebauer 68c8b80
addressed remaining PR comments
dlebauer 4bbc105
applied inline suggestions
divine7022 0485176
fix typos
divine7022 d3a8fb4
remove redundent line
divine7022 42ae7e8
formate table
divine7022 a0f6605
fix typo
divine7022 5272ddd
after logging an error, the script continues execution. added logger.…
divine7022 24d65d3
applied inline suggestions
divine7022 25acf84
applied inline suggestion
divine7022 657b531
silently suppressing source errors with try mask issues; added tryCat…
divine7022 7575cde
added additional test cases to cover: (1) vector inputs to ensure vec…
divine7022 ae4b8ef
specified annual_init unit and made doc clear
divine7022 38086fd
Update scripts/040_downscale.R
dlebauer 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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,33 +1,44 @@ | ||
| # R-specific files | ||
| ########## Operating system / Editor ########## | ||
| .DS_Store | ||
| Thumbs.db | ||
| .vscode/ | ||
| .idea/ | ||
|
|
||
| ########## R / RStudio ########## | ||
| .Rhistory | ||
| .RData | ||
| .Ruserdata | ||
| .Rproj.user/ | ||
| *.Rproj | ||
| Rplots.pdf | ||
|
|
||
| # Quarto directories | ||
| /.quarto/ | ||
| ########## Quarto build outputs ########## | ||
| /_site/ | ||
| /_book/ | ||
| */docs/ | ||
| !/docs/** | ||
| /_freeze/ | ||
| /.quarto/ | ||
| /_files/ | ||
|
|
||
| # Keep only top-level docs; ignore nested docs directories elsewhere | ||
| */docs/ | ||
| !/docs/** | ||
|
|
||
| # VScode settings | ||
| .vscode/ | ||
|
|
||
| # Knitr + R Markdown cache | ||
| ########## Knitr / R Markdown caches ########## | ||
| *_cache/ | ||
| /cache/ | ||
| *_files/ | ||
| *html | ||
|
|
||
| # System files | ||
| .DS_Store | ||
| Thumbs.db | ||
| *.utf8.md | ||
| *.knit.md | ||
| *.html | ||
|
|
||
| # Temporary files | ||
| *.tmp | ||
| ########## Logs / temp ########## | ||
| *.log | ||
| *.tmp | ||
| nohup.out | ||
|
|
||
| ########## Deployment / misc ########## | ||
| rsconnect/ | ||
|
|
||
| ########## Generated figures ########## | ||
| # Do not track any rendered images; site publishing copies from _site | ||
| figures/ |
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 |
|---|---|---|
| @@ -1,78 +1,107 @@ | ||
| ### Workflow Configuration Settings ### | ||
|
|
||
| # Check that we are in the correct working directory | ||
| if(!basename(here::here(getwd())) == 'downscaling') { | ||
| PEcAn.logger::logger.error("Please run this script from the 'downscaling' directory") | ||
| } | ||
|
|
||
| ## Global configuration settings for logging | ||
| parser <- argparse::ArgumentParser() | ||
|
|
||
| options( | ||
| # Print all tibble columns | ||
| tibble.width = Inf, | ||
| # Suppress readr::read_csv messages | ||
| readr.show_col_types = FALSE | ||
| ## Set development vs production mode ## | ||
| # Dev mode speeds up workflows by subsetting data for testing and debugging | ||
| parser$add_argument("--production", | ||
| type = "logical", default = TRUE, | ||
| help = "Set to true for production mode, false for faster development (default: TRUE)" | ||
| ) | ||
| args <- parser$parse_args() | ||
| PRODUCTION <- args$production | ||
|
|
||
| # Manual override for interactive sessions | ||
| if (rlang::is_interactive()) { | ||
| PRODUCTION <- TRUE | ||
| } | ||
|
|
||
| ## Set parallel processing options | ||
| no_cores <- max(future::availableCores() - 1, 1) | ||
| future::plan(future::multicore, workers = no_cores) | ||
|
|
||
|
|
||
| # **set ccmmf_dir and pecan_outdir** | ||
| # Define the CCMMF directory from environment variable | ||
| ccmmf_dir <- Sys.getenv("CCMMF_DIR") | ||
| if (ccmmf_dir == "") { | ||
| ccmmf_dir <- "/projectnb2/dietzelab/ccmmf" | ||
| if (!dir.exists(ccmmf_dir)) { | ||
| # Fallback to repository root for local development | ||
| ccmmf_dir <- here::here() | ||
| } | ||
| } | ||
| pecan_outdir <- file.path(ccmmf_dir, "modelout", "ccmmf_phase_2b_mixed_pfts_20250701") | ||
| # **Is this a test or production run?** | ||
| # Set to FALSE during testing and development | ||
| # | ||
| # Global switch to toggle between fast, small scale runs for development and testing | ||
| # and full-scale production runs. Works by subsetting various data objects. | ||
| PRODUCTION <- TRUE | ||
|
|
||
| # PEcAn model output to be analyzed | ||
| pecan_archive_tgz <- file.path(ccmmf_dir, "lebauer_agu_2025_20251210.tgz") | ||
|
|
||
| # **Variables to extract** | ||
| # see docs/workflow_documentation.qmd for complete list of outputs | ||
| outputs_to_extract <- c( | ||
| "TotSoilCarb", | ||
| "AGB" | ||
| ) | ||
|
|
||
| if(!PRODUCTION) { | ||
| outputs_to_extract <- c("TotSoilCarb", "AGB") | ||
| if (!PRODUCTION) { | ||
| # can subset for testing | ||
| # depending on what part of the workflow you are testing | ||
| # outputs_to_extract <- outputs_to_extract[1] | ||
| outputs_to_extract <- outputs_to_extract[1] | ||
| } | ||
|
|
||
| ### Configuration Settings that can be set to default ### | ||
|
|
||
| # Assume consistent directory structure for other directories | ||
| data_dir <- file.path(ccmmf_dir, "data") | ||
| data_dir <- file.path(ccmmf_dir, "data") | ||
| raw_data_dir <- file.path(ccmmf_dir, "data_raw") | ||
| cache_dir <- file.path(ccmmf_dir, "cache") | ||
| model_outdir <- file.path(pecan_outdir, "out") | ||
| model_outdir <- file.path(pecan_outdir, "out") | ||
|
|
||
| # Misc | ||
| set.seed(42) | ||
| ca_albers_crs <- 3310 | ||
|
|
||
| #### Messagees #### | ||
| PEcAn.logger::logger.info("\n\n", | ||
| "##### SETTINGS SUMMARY #####\n\n", | ||
| "Running in", ifelse(PRODUCTION, "**production**", "**development**"), "mode\n\n", | ||
| "### Directory Settings ###\n", | ||
| "- CCMMF directory:", ccmmf_dir, "\n", | ||
| "- data_dir :", data_dir, "\n", | ||
| "- cache_dir :", cache_dir, "\n", | ||
| "- raw_data_dir. :", raw_data_dir, "\n", | ||
| "- pecan_outdir. :", pecan_outdir, "\n", | ||
| "- model_outdir. :", model_outdir, "\n\n", | ||
| "### Other Settings ###\n", | ||
| "- will extract variables:", paste(outputs_to_extract, collapse = ", "), "\n", | ||
| "- ca_albers_crs :", ca_albers_crs, | ||
| ifelse(ca_albers_crs == 3310, ", which is NAD83 / California Albers", ""), "\n", | ||
| wrap = FALSE | ||
| ) | ||
| ca_albers_crs <- "EPSG:3310" | ||
|
|
||
| if (terra::is.lonlat(ca_albers_crs)) { | ||
| PEcAn.logger::logger.severe("`ca_albers_crs` must remain a projected CRS (EPSG:3310).") | ||
| } | ||
|
|
||
| ca_albers_info <- terra::crs(ca_albers_crs, describe = TRUE) | ||
| if (is.null(ca_albers_info$code) || ca_albers_info$code != 3310) { | ||
| PEcAn.logger::logger.severe("`ca_albers_crs` is reserved for California's standard NAD83 / California Albers (EPSG:3310).") | ||
| } | ||
| ca_albers_name <- ca_albers_info[["name"]] | ||
|
|
||
| #### Messages #### | ||
| ## Ensure pecan_archive_force is defined (allow override via env var) | ||
| if (!exists("pecan_archive_force", inherits = FALSE)) { | ||
| pecan_archive_force <- isTRUE(as.logical(Sys.getenv("PECAN_ARCHIVE_FORCE", "FALSE"))) | ||
| } | ||
|
|
||
| msg <- glue::glue( | ||
| "\n\n", | ||
| "##### SETTINGS SUMMARY #####\n\n", | ||
| "Running in {ifelse(PRODUCTION, '**PRODUCTION**', '**DEVELOPMENT**')} mode\n\n", | ||
| "### Directory Settings ###\n", | ||
| "- CCMMF directory: {ccmmf_dir}\n", | ||
| "- data_dir : {data_dir}\n", | ||
| "- cache_dir : {cache_dir}\n", | ||
| "- raw_data_dir. : {raw_data_dir}\n", | ||
| "- pecan_outdir. : {pecan_outdir}\n", | ||
| "- model_outdir. : {model_outdir}\n\n", | ||
| "- pecan_archive_tgz : {ifelse(is.na(pecan_archive_tgz), '<unset>', pecan_archive_tgz)}\n", | ||
| "- pecan_archive_force : {pecan_archive_force}\n\n", | ||
| "### Other Settings ###\n", | ||
| "- will extract variables: {paste(outputs_to_extract, collapse = ', ')}\n", | ||
| "- ca_albers_crs : {ca_albers_crs}{if(!is.na(ca_albers_name)) paste0(' (', ca_albers_name, ')') else ''}\n" | ||
| ) | ||
| if (!isTRUE(getOption("ccmmf.quiet_banner", FALSE))) { | ||
| PEcAn.logger::logger.info(msg, wrap = FALSE) | ||
| } | ||
|
|
||
| ## Global configuration settings for logging | ||
|
|
||
| options( | ||
| # Print all tibble columns | ||
| tibble.width = Inf, | ||
| # Suppress readr::read_csv messages | ||
| readr.show_col_types = FALSE | ||
| ) | ||
|
|
||
| ## Source all R scripts in the R/ directory | ||
| r_scripts <- list.files(file.path(here::here(), "R"), pattern = "\\.R$", full.names = TRUE) | ||
| lapply(r_scripts, source) |
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,129 @@ | ||
| #' Combine two-PFT outputs to represent mixed cropping systems | ||
| #' | ||
| #' Rules for combining woody and annual PFT outputs (stocks or accumulated | ||
| #' flux totals) to represent a mixed cropping system. Supports two methods: | ||
| #' - "weighted": PFTs partition area (woody_cover + annual_cover = 1) and the | ||
| #' output is a weighted average: `woody_cover * woody_value + annual_cover * annual_value`. | ||
| #' - "incremental": preserves the full-area woody baseline (requires `woody_cover == 1`) | ||
| #' and treats annual as an increment relative to an annual initial baseline: | ||
| #' `annual_delta = annual_value - annual_init`; `result = woody_value + annual_cover * annual_delta`. | ||
| #' | ||
| #' All inputs must be vectors each of length 1 or a shared common length. | ||
| #' | ||
| #' This function is intended to be applied to pools or integrated fluxes (e.g., annual NPP, annual N2O flux). | ||
| #' | ||
| #' Validation rules (severe errors unless otherwise noted): | ||
| #' * No input values may be NA (including covers, pool sizes, annual_init if required) | ||
| #' * Covers must lie within [0,1] | ||
| #' * Method "incremental": `woody_cover` must be 1 (within 0.1%); if not, severe error | ||
| #' * Method "incremental": `annual_init` required | ||
| #' * Method "weighted": rows whose `woody_cover + annual_cover` differ from 1 by more than tolerance | ||
| #' are set to NA in the result; a single aggregated warning is emitted listing the count | ||
| #' | ||
| #' Inputs may be any quantity expressed per unit area (stocks such as | ||
| #' kg/m2 or fluxes accumulated over a defined time step, e.g., kg/m2 per | ||
| #' hour or year). | ||
| #' | ||
| #' @param woody_value numeric. Pool or accumulated flux for the woody PFT (kg/m2). | ||
| #' @param annual_value numeric. Pool or accumulated flux for the annual PFT (kg/m2). | ||
| #' @param annual_init numeric, required for method = "incremental"; the initial annual pool | ||
| #' size at t0 (kg C m-2). Used to compute the delta: (annual_value - annual_init). | ||
| #' @param annual_cover numeric. Fractional cover of the annual PFT (0-1). | ||
| #' @param woody_cover numeric. Fractional cover of the woody PFT (0-1). Must be 1 when `method` is "incremental". | ||
| #' @param method character. One of "weighted" or "incremental". | ||
| #' | ||
| #' @return numeric vector of combined values. | ||
| #' | ||
| #' @examples | ||
| #' # Discrete mixing (weights sum to 1) | ||
| #' combine_mixed_crops( | ||
| #' woody_value = 100, annual_value = 50, | ||
| #' annual_cover = 0.2, woody_cover = 0.8, method = "weighted" | ||
| #' ) | ||
| #' | ||
| #' # Overlap: preserve woody baseline (woody_cover==1), add annual increment scaled by cover | ||
| #' combine_mixed_crops( | ||
| #' woody_value = 200, annual_value = 220, annual_init = 200, | ||
| #' annual_cover = 0.3, woody_cover = 1.0, method = "incremental" | ||
| #' ) | ||
| combine_mixed_crops <- function(woody_value, | ||
| annual_value, | ||
| annual_cover, | ||
| woody_cover, | ||
| annual_init = NULL, | ||
| method = c("weighted", "incremental")) { | ||
| method <- match.arg(method) | ||
|
|
||
| # Internal tolerance for floating point comparisons | ||
| tol <- 1e-3 | ||
|
|
||
| # Accept scalars for cover and vectors for values | ||
| recycled <- vctrs::vec_recycle_common( | ||
| woody_value = woody_value, | ||
| annual_value = annual_value, | ||
| annual_cover = annual_cover, | ||
| woody_cover = woody_cover, | ||
| annual_init = annual_init, | ||
| .size = vctrs::vec_size_common( | ||
| woody_value, | ||
| annual_value, | ||
| annual_cover, | ||
| woody_cover, | ||
| annual_init | ||
| ) | ||
| ) | ||
|
|
||
| woody_value <- recycled$woody_value | ||
| annual_value <- recycled$annual_value | ||
| annual_cover <- recycled$annual_cover | ||
| woody_cover <- recycled$woody_cover | ||
| annual_init <- recycled$annual_init | ||
|
|
||
| # NA checks (annual_init only if required for incremental) | ||
| na_checks <- list( | ||
| woody_value = woody_value, | ||
| annual_value = annual_value, | ||
| annual_cover = annual_cover, | ||
| woody_cover = woody_cover | ||
| ) | ||
| if (method == "incremental") { | ||
| if (is.null(annual_init)) { | ||
| PEcAn.logger::logger.severe("incremental: annual_init is required but missing.") | ||
| } | ||
| na_checks$annual_init <- annual_init | ||
| } | ||
| na_found <- vapply(names(na_checks), function(nm) anyNA(na_checks[[nm]]), logical(1)) | ||
| if (any(na_found)) { | ||
| fields <- paste(names(na_found)[na_found], collapse = ", ") | ||
| PEcAn.logger::logger.severe(paste0("NA values not allowed in inputs: ", fields)) | ||
| } | ||
|
|
||
| # Range checks for covers | ||
| out_of_range_annual <- (annual_cover < -tol) | (annual_cover > 1 + tol) | ||
| out_of_range_woody <- (woody_cover < -tol) | (woody_cover > 1 + tol) | ||
| if (any(out_of_range_annual | out_of_range_woody, na.rm = TRUE)) { | ||
| n_bad <- sum(out_of_range_annual | out_of_range_woody, na.rm = TRUE) | ||
| PEcAn.logger::logger.severe("weighted: cover fractions must be in the range [0,1] (+/- tol). ", n_bad, " rows violate this constraint.") | ||
| } | ||
|
|
||
| if (method == "incremental") { | ||
| not_one <- abs(woody_cover - 1) > tol | ||
| if (any(not_one, na.rm = TRUE)) { | ||
| n_bad <- sum(not_one, na.rm = TRUE) | ||
| PEcAn.logger::logger.severe("incremental: woody_cover must be 1 (+/- ", tol, "); ", n_bad, " rows violate.") | ||
| } | ||
| res <- woody_value + annual_cover * (annual_value - annual_init) | ||
| return(res) | ||
| } | ||
|
|
||
| # weighted method | ||
| sum_cov <- woody_cover + annual_cover | ||
| bad_sum <- abs(sum_cov - 1) > tol | is.na(sum_cov) | ||
| res <- woody_cover * woody_value + annual_cover * annual_value | ||
| if (any(bad_sum, na.rm = TRUE)) { | ||
| n_bad <- sum(bad_sum, na.rm = TRUE) | ||
| PEcAn.logger::logger.warn(paste0("weighted: ", n_bad, " rows with cover fractions not summing to 1 (<U+00B1>tol); results set to NA for those rows.")) | ||
| res[bad_sum] <- NA_real_ | ||
| } | ||
| return(res) | ||
| } | ||
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.
Uh oh!
There was an error while loading. Please reload this page.