Skip to content

Conversation

@Alomir
Copy link
Collaborator

@Alomir Alomir commented Nov 25, 2025

Second (of three) PRs for #182

This PR:

  • Adds CN ratio params for leaf, wood, and fine root (wood is used for coarse root)
  • Adds fluxes for nOrgLitter and nOrgSoil, and updates those pools with the flux results
  • Event handling ignores nitrogen when nitrogen-cycle is off, and warns if fertilization events have non-zero N amounts
  • Makes litter-pool required for nitrogen-cycle to be on
  • Tweaks sipnet.out format to be more readable (should parse the same way)
  • Updates tests

Here's the smoke_check output for russell_2 with these changes:

**********************
Running test russell_2
**********************
     soilOrgN           litterOrgN         
          old       new        old      new
0       135.0  134.9993       14.0  13.9994
1       135.0  134.9985       14.0  13.9987
2       135.0  134.9976       14.0  13.9980
3       135.0  134.9967       14.0  13.9973
4       135.0  134.9958       14.0  13.9966
...       ...       ...        ...      ...
5843    135.0   73.5237       44.0  10.6355
5844    135.0   73.5223       44.0  10.6343
5845    135.0   73.5209       44.0  10.6332
5846    135.0   73.5196       44.0  10.6320
5847    135.0   73.5182       44.0  10.6309

[5848 rows x 4 columns]
Difference Summary:
           first diff total diffs   old mean    new mean
soilOrgN            0        5848      135.0  103.039779
litterOrgN          0        5848  40.060192   18.561273

That is, a slow decline in organic N for both soil and litter. Please comment below if that does not make sense.

Base automatically changed from SIP182a-org-N-pools-and-fert to master December 3, 2025 20:36
@Alomir Alomir marked this pull request as ready for review December 10, 2025 17:28
Copy link

Copilot AI left a 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;
Copy link

Copilot AI Dec 17, 2025

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.

Copilot uses AI. Check for mistakes.
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 "
Copy link

Copilot AI Dec 17, 2025

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.

Suggested change
fprintf(out, "fluxestranspiration minN soilOrgN litterOrgN n2oFlux "
fprintf(out, "fluxesTranspiration minN soilOrgN litterOrgN n2oFlux "

Copilot uses AI. Check for mistakes.
$$

$$\small j \in \{\text{soil, litter}\}$$
This is used to calculate C:N-dependency $D_{CN}$ used in Eq. \eqref{eq:cn_dep}.
Copy link

Copilot AI Dec 17, 2025

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 $D_{CN}$ is actually used in the heterotrophic respiration formula.

Suggested change
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}.

Copilot uses AI. Check for mistakes.
$$\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})}$
Copy link

Copilot AI Dec 17, 2025

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).

Suggested change
### $\frak{C:N \ Dependency \ Function \ (D_{CN})}$
### C:N Dependency Function ($D_{CN}$)

Copilot uses AI. Check for mistakes.
// 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;
Copy link

Copilot AI Dec 17, 2025

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.

Copilot uses AI. Check for mistakes.
Comment on lines +1263 to +1275
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;
Copy link

Copilot AI Dec 17, 2025

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.

Suggested change
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;
}

Copilot uses AI. Check for mistakes.
}

if (ctx.nitrogenCycle && !ctx.litterPool) {
logError("nitrogen-cycle require litter-pool to be turned on\n");
Copy link

Copilot AI Dec 17, 2025

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').

Suggested change
logError("nitrogen-cycle require litter-pool to be turned on\n");
logError("nitrogen-cycle requires litter-pool to be turned on\n");

Copilot uses AI. Check for mistakes.
Comment on lines +457 to +458
fluxes.eventOrgN += 0;
fluxes.eventMinN += 0;
Copy link

Copilot AI Dec 17, 2025

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.

Suggested change
fluxes.eventOrgN += 0;
fluxes.eventMinN += 0;

Copilot uses AI. Check for mistakes.
// 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 +
Copy link

Copilot AI Dec 17, 2025

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.

Suggested change
fluxes.nOrgSoil = (fluxes.litterToSoil - fluxes.rSoil) / soilCN +
fluxes.nOrgSoil = fluxes.litterToSoil / litterCN - fluxes.rSoil / soilCN +

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants