Account for stock clamping in mass balance check#282
Conversation
There was a problem hiding this comment.
Pull request overview
Updates SIPNET’s mass balance checking to account for mass introduced when negative stocks are clamped to zero, preventing false fatal balance errors during edge-case runs (e.g., harvest + extreme parameters).
Changes:
- Track C/N totals before
ensureNonNegativeStocks()and compute per-timestep “clamped mass” adjustments. - Subtract clamped adjustments from balance deltas and emit a warning when clamping occurs.
- Update smoke-test golden output and add a changelog entry.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
src/sipnet/sipnet.c |
Captures pre-clamp mass totals, computes clamp adjustments, and logs a warning when clamping happens. |
src/sipnet/balance.h |
Extends BalanceTracker with clampedC/clampedN and exposes getMassTotals(). |
src/sipnet/balance.c |
Initializes clamp fields and adjusts delta calculations to account for clamped mass. |
tests/smoke/russell_2/sipnet.out |
Updates expected bcdeltaC/bcdeltaN values to reflect the corrected balance logic. |
docs/CHANGELOG.md |
Notes the balance-check behavior change under “Fixed”. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (1)
src/sipnet/balance.c:160
checkBalance()now logs warnings but never setserr = 1(and the internal-error calls are commented out), so the model will never exit on balance failure. This is a significant behavioral change that isn’t described in the PR summary, and the current mix of commented-out code + deadif (err)block is confusing. Please either restore the intended fatal behavior (seterrwhen deltas exceed tolerance) or remove theerr/exit path and update the PR description to reflect that balance failures are non-fatal.
int err = 0;
if (fabs(balanceTracker.deltaC) > 0.0) {
// err = 1;
// logInternalError( someday
logWarning(
"Carbon balance check failed (delta=%8.4f, Y: %d D: %d T: %4.2f)\n",
balanceTracker.deltaC, climate->year, climate->day, climate->time);
}
if (fabs(balanceTracker.deltaN) > 0.0) {
// err = 1;
// logInternalError( someday
logWarning(
"Nitrogen balance check failed (delta=%8.4f, Y: %d D: %d T: %4.2f)\n",
balanceTracker.deltaN, climate->year, climate->day, climate->time);
logWarning("preTot %8.5f postTot %8.5f input %8.5f output %8.5f clamped "
"%8.5f delta %8.5f\n",
balanceTracker.preTotalN, balanceTracker.postTotalN,
balanceTracker.inputsN, balanceTracker.outputsN,
balanceTracker.clampedN, balanceTracker.deltaN);
}
if (err) {
logInternalError("Exiting\n");
exit(EXIT_CODE_INTERNAL_ERROR);
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| logWarning( | ||
| "Non-negative stock constraint applied for %s (value %8.4f set " | ||
| "to %8.4f)\n", | ||
| label, *var, minVal); |
There was a problem hiding this comment.
The clamping warning message says the value is set to minVal, but the code always assigns *var = 0.. This makes the log output misleading (e.g., for snow where minVal is TINY). Consider either setting *var = minVal or updating the message to report the actual value being assigned (0.0).
| label, *var, minVal); | |
| label, *var, 0.0); |
There was a problem hiding this comment.
Yep, I was thinking it actually set it to TINY, but that's not the case.
Summary
ensureNonNegativeStocks()clamps negative pool to zero, the balance checker now accounts for added mass instead of treating it as an error. A warning is logged when clamping occurs.the balance check compared pool deltas against flux predicted deltas without accounting for mass added by stock clamping. This caused a fatal error any time a pool went slightly negative -- common during harvest events with extreme parameter combinations (e.g. SA or ensemble runs); had existing TODO acknowledging this problem
How was this change tested?
Run
python tools/smoke_check.py helpfor more info.Reproduction steps
run sipnet with a parameter set and events file that causes a pool to go slightly negative (e.g. high turnover rate + full harvest). The balance check will fail with a non zero delta equal to the clamped amount
If appropriate, list steps to reproduce the change locally
Related issues
Checklist
docs/CHANGELOG.mdupdated with noteworthy changesclang-format(rungit clang-formatif needed)Note: See CONTRIBUTING.md for additional guidance. This repository uses automated formatting checks; if the pre-commit hook blocks your commit, run
git clang-formatto format staged changes.