SIP208, SIP209 Add carbon balance check#248
Conversation
There was a problem hiding this comment.
Pull request overview
This pull request implements carbon and nitrogen balance checking infrastructure to address issues #208 and #209. The changes separate the NPP storage concept into its own pool (distinct from plantWoodC) to enable proper nitrogen balance tracking, add comprehensive mass balance verification, and implement event tracking for leaf-on and leaf-off phenology.
Changes:
- New balance checking infrastructure (balance.c/h) that calculates and verifies total system carbon and nitrogen at each timestep
- Separation of nppStorage from plantWoodC pool to enable nitrogen balance calculations
- Addition of explicit event flux tracking for mass balance (eventInputC, eventOutputC, eventInputN, eventOutputN)
- Enhanced event output to include computed leaf-on and leaf-off events
- New test suite (testBalance.c) and smoke test improvements
Reviewed changes
Copilot reviewed 18 out of 23 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/sipnet/balance.c | New file implementing mass balance tracking and verification logic |
| src/sipnet/balance.h | New file defining balance tracker structure and interface |
| src/sipnet/state.h | Added nppStorage pool and event mass balance flux fields |
| src/sipnet/sipnet.c | Refactored NPP storage model, added balance checking to pool updates, enhanced output |
| src/sipnet/events.c | Added mass balance tracking for events, new writeComputedEventOut function |
| src/sipnet/events.h | Added writeComputedEventOut function declaration |
| src/sipnet/cli.c | Minor documentation clarification for do-single-outputs |
| tools/smoke_check.py | Added 'base' comparison option and updated column list for new outputs |
| tests/smoke/*/events.out | Updated with new leaf-on and leaf-off event outputs |
| tests/sipnet/test_modeling/testBalance.c | New comprehensive balance checking test |
| tests/sipnet/test_modeling/balance.param | Test parameter file |
| tests/sipnet/test_modeling/balance.clim | Test climate file |
| tests/sipnet/test_modeling/Makefile | Added testBalance to build |
| Makefile | Added balance.c to SIPNET_CFILES |
| CMakeLists.txt | Added balance.c and testBalance.c to builds |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Looks good. It is reassuring to have these checks (I think title should be C and N checks?).
I just discussed this with @Alomir online, and am noting down a few key decisions and rationale.
-
The N demand associated with leafOn can be met by the soil mineral N pool, constrained by the N limitation dynamics.
- If this causes the model to behave in unexpected ways (e.g. N limitation is too strong to meet demand for leafOn, and then plants are unable to grow realistically), we can revisit this assumption.
- A more physiologically realistic approach would be to have N translocated from leaves to a N storage pool prior to leafOff, then use that storage to support leafOn. We decided against that because plant biomass has fixed C:N in the current version of SIPNET.
-
Rename
envi.nppStorageto better reflect that this is a carbon storage or non-structural carbohydrate delta (without N). We discussed usingwoodNSCDeltabut I'm leaning toward 'storage' since it is an abstract bookkeeping term, while NSC is directly measurable. e.g.woodCStorageDelta. -
Currently, plantWoodC implicitly includes the
nppStoragestorage delta. To explicitly track the storage delta and maintain existing behavior, any fluxes that use total wood C need to replaceplantWoodCwith(plantWoodC + nppStorage)where it is meant to represent total woodC e.g. calculations of flux and allocation. But only use plantWoodC in N accounting.
src/sipnet/state.h
Outdated
| // litter (organic) nitrogen pool (g N m^-2 ground area) | ||
| double litterN; | ||
|
|
||
| ///// New to SIPNET |
There was a problem hiding this comment.
maybe time to bump a version and say 'added to SIPNET in v2.1'
src/sipnet/balance.c
Outdated
| balanceTracker.deltaN = 0.0; | ||
| } | ||
|
|
||
| // TBD: warn if balance off? |
There was a problem hiding this comment.
Any reason not to error? Seems like this is a failure here.
dlebauer
left a comment
There was a problem hiding this comment.
Looks good to me. I fixed one typo, made one suggestion to comments for clarity (but I am not certain it is correct), and asked whether we should write out event fluxes that are 0.
@copilot please suggest updates to the documentation (model-structure.md, parameters.md) and CHANGELOG.md.
| } | ||
|
|
||
| if (ctx.nitrogenCycle) { | ||
| // Note: this is the one place where we ust plantWoodC by itself; it's the |
There was a problem hiding this comment.
| // Note: this is the one place where we ust plantWoodC by itself; it's the | |
| // Note: this is the one place where we use plantWoodC by itself; it's the |
| @@ -1,14 +1,14 @@ | |||
| 1999 184 leafon fluxes.leafCreation=0.00 | |||
| 1999 184 leafon fluxes.leafOnCreation=0.00 | |||
There was a problem hiding this comment.
should these be 0? should they be written out if they are 0?
| // here that must be tracked. So, we split the wood pool into plantWoodC | ||
| // (above) and a new pool to track non-nitrogen-affecting changes over time. | ||
| // As this is a delta, it can be negative. Note that the actual "wood carbon" | ||
| // is the sum of these two pools. |
There was a problem hiding this comment.
I'm wondering is if there is a more clear way to define "non-nitrogen-affecting changes". My first thought is that this is separating out NPP + allocation as C-only processes? However, N affects both, and both of these eventually affect N (e.g. through demand and allocation).
| // here that must be tracked. So, we split the wood pool into plantWoodC | |
| // (above) and a new pool to track non-nitrogen-affecting changes over time. | |
| // As this is a delta, it can be negative. Note that the actual "wood carbon" | |
| // is the sum of these two pools. | |
| // here that must be tracked. So, we split the wood pool into plantWoodC | |
| // (above) and a new pool, plantWoodCStorage, to track non-nitrogen-affecting | |
| // changes over time. | |
| // As this is a delta, it can be negative. Note that the actual "wood carbon" | |
| // is the sum of these two pools. | |
| // plantWoodCStorage term was previously named nppStorage and tracked implicitly; | |
| // tracking it explicitly allows SIPNET to handle NPP and allocation prior to | |
| // accounting for N dynamics. |
|
@copilot Can you provide a final review of this PR? |
Summary
Buckle up...
General
balance.c|h:sipnet.c:events.outwhen leaf-on or leaf-off is triggeredupdatePoolsAndBalancefunction for all of updateXPools subfunctions plus new balance calcs-n-checksstate.h:Carbon balance
Nitrogen balance
How was this change tested?
I ran smoke tests, reviewed output for general logical sense, and verified that the balance checks worked (modulo known issues with N)
Steps to reproduce and verify the change locally:
See sipnet.out in any smoke test directory
Smoke test check results:
smoke_check.txt
Related issues
Reproduction steps
If appropriate, list steps to reproduce the change locally
Related issues
Checklist
docs/CHANGELOG.mdupdated with noteworthy changesclang-format(rungit clang-formatif needed)For model structure changes:
\frakturfont formatting fromdocs/model-structure.mdfor implemented features