Skip to content

Commit 513e0a0

Browse files
authored
Merge pull request #4048 from ANAMASGARD/GH-4007-segment-dataframe-empty-after-filter
fix(PEcAn.SIPNET): return empty dataframe when date filtering removes all segments (#4007)
2 parents 770075d + 0a90234 commit 513e0a0

4 files changed

Lines changed: 107 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ For more information about this file see also [Keep a Changelog](http://keepacha
3636
- Added note to DEV-INTRO.md documenting Traefik workaround for Apple Silicon (ARM64) Macs: use `traefik:v2.11` with `platform: linux/arm64` to fix 404 errors (#3910)
3737
- Fixed `web/08-finished.php`: show database info instead of "Still running" when workflow folder doesn't exist locally (#3501).
3838
- `PEcAn.utils::transformstats()`: corrected the LSD-to-SE conversion. The previous implementation included an extra `sqrt(n)` factor, causing SE estimates derived from LSD to appear `sqrt(n)` times smaller than they should be, non-conservatively over-weighting those observations in meta-analysis. (#3998)
39+
- `segment_dataframe()` now returns an empty dataframe when date filtering removes all crop-cycle segments, instead of a single row with NA columns that caused downstream segment config errors (#4007).
3940

4041
### Changed
4142
- `PEcAn.uncertainty::get.parameter.samples()`: replaced the `save_to_disk` flag (from #3860) with an `outdir` argument (default `settings$outdir`) controlling whether `samples.Rdata` is written; `outdir = NULL` skips the save. Existing callers are unaffected (@omkarrr2533, #4016)

models/sipnet/NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
Sipnet version `sipnet.unk`.
2828
* Added trait to parameter mappings for 13 nitrogen cycle parameters in `write.config.SIPNET`,
2929
including tissue C:N ratios, N volatilization/leaching, fixation, and methane rates.
30+
* `segment_dataframe()` now returns an empty dataframe when date filtering removes all crop-cycle segments, instead of a single row with NA columns that caused downstream segment config errors (#4007).
3031

3132

3233
# PEcAn.SIPNET 1.10.0

models/sipnet/R/write_segmented_configs.R

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -160,12 +160,12 @@ segment_dataframe <- function(run_settings) {
160160
# Also clip the end date to avoid edge cases where run_start == end_date
161161
.data$end_date > .env$run_start
162162
)
163-
if (nrow(segments) < 1) {
164-
PEcAn.logger::logger.error(
165-
"Filtering resulted in no segments. ",
166-
"This is an invalid state; check settings and events.json."
167-
)
168-
}
163+
if (nrow(segments) == 0) {
164+
PEcAn.logger::logger.info(
165+
"no crop cycle segments fall within the run window; running unsegmented"
166+
)
167+
return(segments)
168+
}
169169
segments[1, "start_date"] <- run_start
170170
}
171171

models/sipnet/tests/testthat/test-write_segmented_configs.R

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,102 @@ test_that("write_segmented_configs", {
9898
expect_match(jobsh, "bash .*segment_002/run/1/job.sh", all = FALSE)
9999
expect_match(jobsh, "bash .*segment_003/run/1/job.sh", all = FALSE)
100100
})
101+
102+
test_that("segment_dataframe returns empty when run start is after all crop cycles", {
103+
pth <- withr::local_tempdir()
104+
crp_chg_path <- file.path(pth, "cycles-a.csv")
105+
c("date,crop_code", "2025-01-02,D12", "2025-01-05,G6") |>
106+
writeLines(crp_chg_path)
107+
108+
run_settings <- PEcAn.settings::as.Settings(list(
109+
run = list(
110+
site = list(id = "a", site.pft = list(veg = "pft1")),
111+
inputs = list(crop_changes = list(path = crp_chg_path)),
112+
start.date = "2025-01-10",
113+
end.date = "2025-01-20"
114+
)
115+
))
116+
117+
result <- PEcAn.SIPNET:::segment_dataframe(run_settings)
118+
119+
expect_equal(nrow(result), 0)
120+
expect_named(
121+
result,
122+
c("start_date", "crop_code", "end_date")
123+
)
124+
})
125+
126+
test_that("write_segment_configs returns unaltered job.sh when no segments remain", {
127+
pth <- withr::local_tempdir()
128+
129+
event_lines <- "2025 1 irrig 0 1"
130+
event_src_path <- file.path(pth, "events-a.in")
131+
met_path <- file.path(pth, "a.clim")
132+
crp_chg_path <- file.path(pth, "cycles-a.csv")
133+
run_path <- file.path(pth, "run", "ENS-00001-a")
134+
dir.create(run_path, recursive = TRUE)
135+
136+
event_lines |>
137+
writeLines(con = event_src_path)
138+
c("date,crop_code", "2025-01-02,D12", "2025-01-05,G6") |>
139+
writeLines(crp_chg_path)
140+
c("run_id,site_id", "ENS-00001-a,a") |>
141+
writeLines(file.path(pth, "runs_manifest.csv"))
142+
data.frame(
143+
year = 2025,
144+
day = rep(1:31, each = 4),
145+
hour = rep(c(0, 6, 12, 18), 31),
146+
c4 = NA, c5 = NA, c6 = NA, c7 = NA, c8 = NA, c9 = NA, c10 = NA, c11 = NA,
147+
c12 = NA
148+
) |>
149+
write.table(
150+
file = met_path, quote = FALSE,
151+
row.names = FALSE, col.names = FALSE
152+
)
153+
ens.samples <- list(
154+
pft1 = data.frame(Amax = 1, SLA = 2),
155+
soil = data.frame(Rd = 0)
156+
)
157+
save(ens.samples, file = file.path(pth, "ensemble.samples.testid.Rdata"))
158+
159+
s <- PEcAn.settings::as.Settings(
160+
list(
161+
outdir = file.path(pth),
162+
rundir = file.path(pth, "run"),
163+
modeloutdir = file.path(pth, "out"),
164+
pfts = list(pft1 = list(), soil = list()),
165+
ensemble = list(ensemble.id = "testid"),
166+
model = list(binary = "", revision = "2.1.0"),
167+
run = list(
168+
site = list(id = "a", name = "site1", lat = 40, lon = -88,
169+
site.pft = list(veg = "pft1", soil = "soil")),
170+
inputs = list(
171+
met = list(path = met_path),
172+
events = list(path = event_src_path),
173+
crop_changes = list(path = crp_chg_path)
174+
),
175+
start.date = "2025-01-10",
176+
end.date = "2025-01-20"
177+
),
178+
host = list(
179+
name = "localhost",
180+
outdir = file.path(pth),
181+
rundir = file.path(pth, "run")
182+
)
183+
)
184+
)
185+
186+
write.config.SIPNET(
187+
defaults = s$pfts,
188+
trait.values = ens.samples["pft1"],
189+
IC = list(soil = 3.14),
190+
settings = s,
191+
run.id = "ENS-00001-a"
192+
)
193+
194+
seg_res <- write_segmented_configs.SIPNET(settings = s)
195+
196+
expect_equal(seg_res, file.path(run_path, "job.sh"))
197+
expect_false(file.exists(file.path(run_path, "job_segmented.sh")))
198+
expect_false(dir.exists(file.path(run_path, "segments")))
199+
})

0 commit comments

Comments
 (0)