-
Notifications
You must be signed in to change notification settings - Fork 20
SIP182b Add litter and soil org n fluxes #199
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
Conversation
…nd-soil-org-N-fluxes
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.
Pull request overview
This PR adds organic nitrogen pool dynamics to SIPNET as part of implementing a nitrogen cycle model. It introduces CN ratio parameters for plant biomass pools, calculates nitrogen fluxes for litter and soil organic matter, and ensures proper handling of nitrogen when the nitrogen-cycle feature is disabled.
Key changes:
- Adds three CN ratio parameters (leafCNRatio, woodCNRatio, rootCNRatio) for converting carbon fluxes to nitrogen fluxes
- Implements
calcOrgNFluxes()to calculate nitrogen dynamics for litter and soil organic matter pools - Updates nitrogen pools in a new
updateNitrogenPools()function separated from carbon pool updates
Reviewed changes
Copilot reviewed 12 out of 17 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
tests/smoke/russell_1/sipnet.param |
Adds CN ratio parameters to test configuration |
tests/smoke/russell_2/sipnet.param |
Adds CN ratio parameters to test configuration |
tests/smoke/russell_2/sipnet.config |
Updates timestamp in auto-generated config |
tests/sipnet/test_modeling/testNitrogenCycle.c |
Refactors test structure and adds organic N pool testing |
tests/sipnet/test_events_types/testEventFertilization.c |
Updates tests to handle nitrogen-cycle toggle correctly |
src/sipnet/state.h |
Adds CN ratio parameters and organic N flux variables, fixes typo |
src/sipnet/sipnet.c |
Implements organic N flux calculations and pool updates, reformats output |
src/sipnet/events.c |
Adds warning and handling for nitrogen when cycle is off |
src/common/context.c |
Validates that litter-pool is required for nitrogen-cycle |
docs/user-guide/model-inputs.md |
Adds reference to parameters documentation |
docs/model-structure.md |
Updates formatting and clarifies nitrogen dynamics equations |
docs/CHANGELOG.md |
Documents new nitrogen cycle features |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/sipnet/sipnet.c
Outdated
| // The litter org N flux is determined by the carbon fluxes from wood and leaf | ||
| // litter, and N loss due to mineralization. N added via fertilization | ||
| // is handled elsewhere. | ||
| litterCN = envi.litter / envi.litterOrgN; |
Copilot
AI
Dec 17, 2025
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.
Potential division by zero: If envi.litterOrgN is zero or very close to zero, the calculation litterCN = envi.litter / envi.litterOrgN on line 1263 will result in division by zero or numerical instability. Similarly for envi.soilOrgN on line 1272. Consider adding validation to ensure these values are positive before performing the division.
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.
for consistent naming, would it make more sense to name these litter variables envi.litterC and envi.litterN?
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.
Yes it would!
src/sipnet/sipnet.c
Outdated
| litterCN = envi.litter / envi.litterOrgN; | ||
| fluxes.nOrgLitter = fluxes.leafLitter / params.leafCNRatio + | ||
| fluxes.woodLitter / params.woodCNRatio - | ||
| fluxes.rLitter / litterCN; | ||
|
|
||
| // soil | ||
| // The soil org N flux is determined by the carbon flux from the litter pool, | ||
| // carbon fluxes from roots, and N loss due to mineralization | ||
| // (Note: woodCNRatio is used for coarse roots) | ||
| soilCN = envi.soil / envi.soilOrgN; | ||
| fluxes.nOrgSoil = (fluxes.litterToSoil - fluxes.rSoil) / soilCN + | ||
| fluxes.fineRootLoss / params.rootCNRatio + | ||
| fluxes.coarseRootLoss / params.woodCNRatio; |
Copilot
AI
Dec 17, 2025
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.
Potential division by zero: If envi.soilOrgN is zero or very close to zero, the calculation soilCN = envi.soil / envi.soilOrgN will result in division by zero or numerical instability. Consider adding validation to ensure this value is positive before performing the division.
| litterCN = envi.litter / envi.litterOrgN; | |
| fluxes.nOrgLitter = fluxes.leafLitter / params.leafCNRatio + | |
| fluxes.woodLitter / params.woodCNRatio - | |
| fluxes.rLitter / litterCN; | |
| // soil | |
| // The soil org N flux is determined by the carbon flux from the litter pool, | |
| // carbon fluxes from roots, and N loss due to mineralization | |
| // (Note: woodCNRatio is used for coarse roots) | |
| soilCN = envi.soil / envi.soilOrgN; | |
| fluxes.nOrgSoil = (fluxes.litterToSoil - fluxes.rSoil) / soilCN + | |
| fluxes.fineRootLoss / params.rootCNRatio + | |
| fluxes.coarseRootLoss / params.woodCNRatio; | |
| if (fabs(envi.litterOrgN) >= TINY) { | |
| litterCN = envi.litter / envi.litterOrgN; | |
| fluxes.nOrgLitter = fluxes.leafLitter / params.leafCNRatio + | |
| fluxes.woodLitter / params.woodCNRatio - | |
| fluxes.rLitter / litterCN; | |
| } else { | |
| // Avoid division by zero or near-zero litterOrgN; omit mineralization term | |
| fluxes.nOrgLitter = fluxes.leafLitter / params.leafCNRatio + | |
| fluxes.woodLitter / params.woodCNRatio; | |
| } | |
| // soil | |
| // The soil org N flux is determined by the carbon flux from the litter pool, | |
| // carbon fluxes from roots, and N loss due to mineralization | |
| // (Note: woodCNRatio is used for coarse roots) | |
| if (fabs(envi.soilOrgN) >= TINY) { | |
| soilCN = envi.soil / envi.soilOrgN; | |
| fluxes.nOrgSoil = (fluxes.litterToSoil - fluxes.rSoil) / soilCN + | |
| fluxes.fineRootLoss / params.rootCNRatio + | |
| fluxes.coarseRootLoss / params.woodCNRatio; | |
| } else { | |
| // Avoid division by zero or near-zero soilOrgN; omit mineralization term | |
| fluxes.nOrgSoil = fluxes.fineRootLoss / params.rootCNRatio + | |
| fluxes.coarseRootLoss / params.woodCNRatio; | |
| } |
src/common/context.c
Outdated
| } | ||
|
|
||
| if (ctx.nitrogenCycle && !ctx.litterPool) { | ||
| logError("nitrogen-cycle require litter-pool to be turned on\n"); |
Copilot
AI
Dec 17, 2025
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.
Grammar error: The error message should be "nitrogen-cycle requires litter-pool to be turned on" (with 's' on 'requires').
| logError("nitrogen-cycle require litter-pool to be turned on\n"); | |
| logError("nitrogen-cycle requires litter-pool to be turned on\n"); |
src/sipnet/events.c
Outdated
| fluxes.eventOrgN += 0; | ||
| fluxes.eventMinN += 0; |
Copilot
AI
Dec 17, 2025
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.
Redundant code: Lines 457-458 explicitly add 0 to the fluxes, which has no effect. These lines can be removed as the fluxes would remain unchanged (either already zero or not modified). This else block can either be empty or contain just a comment explaining the behavior.
| fluxes.eventOrgN += 0; | |
| fluxes.eventMinN += 0; |
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.
I was also confused - is this useful?
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.
Nope, not at all. I wonder if I had something different there at first? A comment here really is better.
src/sipnet/sipnet.c
Outdated
| // carbon fluxes from roots, and N loss due to mineralization | ||
| // (Note: woodCNRatio is used for coarse roots) | ||
| soilCN = envi.soil / envi.soilOrgN; | ||
| fluxes.nOrgSoil = (fluxes.litterToSoil - fluxes.rSoil) / soilCN + |
Copilot
AI
Dec 17, 2025
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.
The calculation appears to have an error in the nitrogen flux accounting. The term (fluxes.litterToSoil - fluxes.rSoil) / soilCN incorrectly divides the litter-to-soil carbon flux by the soil C:N ratio. The nitrogen flux from litter to soil should use the litter C:N ratio (litterCN), while only the soil mineralization term (fluxes.rSoil) should use the soil C:N ratio. The expression should be: fluxes.litterToSoil / litterCN - fluxes.rSoil / soilCN.
| fluxes.nOrgSoil = (fluxes.litterToSoil - fluxes.rSoil) / soilCN + | |
| fluxes.nOrgSoil = fluxes.litterToSoil / litterCN - fluxes.rSoil / soilCN + |
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.
I understand why I wrote it that way, but I think Copilot is correct here. The N transferred to soil is dependent on litterCN, not soilCN.
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.
Looks great! A few minor suggestions, and one critical check / change: please make sure documentation and implementation of root C and N transfer to soil docs are consistent.
src/sipnet/events.c
Outdated
| fluxes.eventOrgN += 0; | ||
| fluxes.eventMinN += 0; |
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.
I was also confused - is this useful?
src/sipnet/sipnet.c
Outdated
| // The litter org N flux is determined by the carbon fluxes from wood and leaf | ||
| // litter, and N loss due to mineralization. N added via fertilization | ||
| // is handled elsewhere. | ||
| litterCN = envi.litter / envi.litterOrgN; |
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.
for consistent naming, would it make more sense to name these litter variables envi.litterC and envi.litterN?
| litterCN = envi.litter / envi.litterOrgN; | ||
| fluxes.nOrgLitter = fluxes.leafLitter / params.leafCNRatio + | ||
| fluxes.woodLitter / params.woodCNRatio - | ||
| fluxes.rLitter / litterCN; |
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.
also for naming consistency, I don't think it is necessary to include Ratio in the names of these params. But if desired, consider adding it to litterCN
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.
Oh, right, good call
docs/model-structure.md
Outdated
| $$\small i \in \{\text{leaf, wood, fine root, coarse root}\}$$ | ||
| $$\small i \in \{\text{leaf, wood}\}$$ | ||
|
|
||
| The flux of nitrogen from living biomass to the litter pool is proportional to the carbon content of the biomass, based on the C:N ratio of the biomass pool \eqref{eq:cn_stoich}. Similarly, nitrogen from organic matter amendments is calculated from the carbon content and the C:N ratio of the inputs. |
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
docs/model-structure.md
Outdated
| <!-- | ||
| _existing equation + harvest transfer and organic matter inputs | ||
| --> | ||
| $$\small i \in \{\text{leaf, wood, fine root, coarse root}\}$$ |
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
dlebauer
left a comment
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.
Update - I've tried to clean up the documentation of C and N routing in #216
Please review / revise / merge and make sure that implementation and docs are consistent.
* Clarify C routing from plant to litter and soil Separate Aboveground biomass --> litter Belowground biomass --> soil For both C and N. * Apply suggestions from code review * Refine text in model-structure documentation Removed unnecessary comments and fixed minor wording issues for clarity. * Update terminology and clarify carbon/nitrogen fluxes Corrected terminology and clarified definitions related to carbon and nitrogen fluxes in the model structure documentation.
dlebauer
left a comment
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.
Looks great!
When implementing plant N it will make sense to use the same convention to separate C and N components.
Second (of three) PRs for #182
This PR:
nitrogen-cycleis off, and warns if fertilization events have non-zero N amountslitter-poolrequired fornitrogen-cycleto be onHere's the smoke_check output for russell_2 with these changes:
That is, a slow decline in organic N for both soil and litter. Please comment below if that does not make sense.