-
Notifications
You must be signed in to change notification settings - Fork 18
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
base: master
Are you sure you want to change the base?
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.
| newEvent->year = year; | ||
| newEvent->day = day; | ||
| newEvent->type = eventType; | ||
| static int nitrogenWarned = 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.
The static variable nitrogenWarned is declared inside the function after other statements. In C, all variable declarations should appear at the beginning of a block before any statements. This variable should be declared before line 36 where assignments begin.
| fprintf(out, "litter soilWater soilWetnessFrac snow "); | ||
| fprintf(out, "npp nee cumNEE gpp rAboveground rSoil " | ||
| "rRoot ra rh rtot evapotranspiration "); | ||
| fprintf(out, "fluxestranspiration minN soilOrgN litterOrgN n2oFlux " |
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.
Typo in header: "fluxestranspiration" should be "fluxesTranspiration" or "fluxes transpiration" (missing space or capitalization). This appears to be a concatenation error.
| fprintf(out, "fluxestranspiration minN soilOrgN litterOrgN n2oFlux " | |
| fprintf(out, "fluxesTranspiration minN soilOrgN litterOrgN n2oFlux " |
| $$ | ||
|
|
||
| $$\small j \in \{\text{soil, litter}\}$$ | ||
| This is used to calculate C:N-dependency $D_{CN}$ used in Eq. \eqref{eq:cn_dep}. |
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 equation reference is incorrect. The text says "used in Eq. \eqref{eq:cn_dep}" but should reference "\eqref{eq:rh}" (Equation 7) which is where
| This is used to calculate C:N-dependency $D_{CN}$ used in Eq. \eqref{eq:cn_dep}. | |
| This is used to calculate C:N-dependency $D_{CN}$ used in Eq. \eqref{eq:rh}. |
| $$\small j \in \{\text{soil, litter}\}$$ | ||
| This is used to calculate C:N-dependency $D_{CN}$ used in Eq. \eqref{eq:cn_dep}. | ||
|
|
||
| ### $\frak{C:N \ Dependency \ Function \ (D_{CN})}$ |
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 heading uses LaTeX \frak formatting that is inconsistent with other headings in this section. This should be changed to plain Markdown formatting like the other headings that were updated in this PR (lines 264, 278, 311, 327, 342, 359, 384).
| ### $\frak{C:N \ Dependency \ Function \ (D_{CN})}$ | |
| ### C:N Dependency Function ($D_{CN}$) |
| // 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.
| 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; | |
| } |
| } | ||
|
|
||
| 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"); |
| 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; |
| // 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 + |
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.