Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
2380796
Add org N handling for fert
Alomir Nov 20, 2025
640bb2e
Adds fields for org N handling
Alomir Nov 20, 2025
5ac872d
Adds organic N pools and params, with output
Alomir Nov 20, 2025
37663f3
Updates for fert org N
Alomir Nov 20, 2025
7137e15
Updates cols list for niwot
Alomir Nov 20, 2025
f8fb01a
Updates for initial org N implementation
Alomir Nov 20, 2025
4c96eb7
Updates to eqs 13 and 14
Alomir Nov 25, 2025
896f99d
Adds litter pool req for nitrogen cycle
Alomir Nov 25, 2025
6f77a31
Initial implementation for organic N pools plus fert updates
Alomir Nov 25, 2025
cc2ef42
Updates to fert for org N pools
Alomir Nov 25, 2025
789f143
Updates for org N pools
Alomir Nov 25, 2025
d58dbad
Merge branch 'master' into SIP182a-org-N-pools-and-fert
Alomir Nov 25, 2025
ddfa75e
Merge branch 'SIP182a-org-N-pools-and-fert' into SIP182b-add-litter-a…
Alomir Nov 25, 2025
73fa60c
Reorg and updates for org N pools
Alomir Nov 25, 2025
a6a08ef
Adds CN ration params
Alomir Nov 25, 2025
48a547f
Tweak output spacing for readability
Alomir Dec 2, 2025
0fd073d
Merge branch 'master' into SIP182b-add-litter-and-soil-org-N-fluxes
Alomir Dec 3, 2025
07dd270
Fix broken link
Alomir Dec 3, 2025
309ea45
Fix merge errors
Alomir Dec 3, 2025
2bf2ea1
Allow russell ranch link
Alomir Dec 3, 2025
71b240b
Brings russell_1 params up to date
Alomir Dec 9, 2025
fb91e8d
Merge branch 'master' into SIP182b-add-litter-and-soil-org-N-fluxes
Alomir Dec 10, 2025
d540a76
Updates for organic N calcs
Alomir Dec 10, 2025
d2fae26
Removes 'frak' font from some implemented sections
Alomir Dec 10, 2025
068021e
Updates for nitrogen cycle additions to date
Alomir Dec 10, 2025
0d09b86
Updates CN text
Alomir Dec 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ sections to include in release notes:
- Soil mineral pool (#170)
- Nitrogen effects of fertilization (#173)
- `logAppend` logging function (#173)
- Nitrogen volatilization (#175)
- Nitrogen leaching (#178)
- Soil and litter organic N pools (#195, #198)
- Organic N handling for fertilization and soil dynamics (#198)

### Fixed

Expand Down
31 changes: 18 additions & 13 deletions docs/model-structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ $$
R_{H} = f_{R_H} \cdot
\left(\sum_j K_j \cdot C_j
\right) \cdot
D_{\text{temp}} \cdot D_{\text{water,}R_H} \cdot D_{CN} \mathfrak{\cdot D_{\text{tillage}}}
D_{\text{temp}} \cdot D_{\text{water,}R_H} \cdot D_{CN} \cdot D_{\text{tillage}}
\tag{7}\label{eq:rh}
$$

Expand All @@ -261,7 +261,7 @@ The calculation of methane flux $(F^C_{CH_4})$ is analagous to to that of $R_H$

The carbon and nitrogen cycle are tightly coupled by the C:N ratios of plant and organic matter pools. The C:N ratio of plant biomass pools is fixed, while the C:N ratio of soil organic matter and litter pools is dynamic.

### $\frak{Fixed \ Plant \ C:N \ Ratios}$
### Fixed Plant C:N Ratios

Plant biomass pools have a fixed CN ratio and are thus stoichiometrically coupled to carbon:

Expand All @@ -275,15 +275,15 @@ Where $i$ is the leaf, wood, fine root, or coarse root pool. This relationship a

Soil organic matter and litter pools have dynamic CN that is determined below.

### $\frak{Dynamic \ Soil \ Organic \ Matter \ and \ Litter \ C:N \ Ratios}$
### Dynamic Soil Organic Matter and Litter C:N Ratios

The change in the soil C:N ratio over time of soil and litter pools depends on the rate of change of carbon and nitrogen in the pool, normalized by the total nitrogen in the pool. This makes sense as it captures how changes in carbon and nitrogen affect their ratio.
In SIPNET, the C:N ratio of soil and litter pools is calculated directly from the carbon and nitrogen pools.

$$
\frac{dCN_{\text{j}}}{dt} = \frac{1}{N_{\text{j}}} \left( \frac{dC_{\text{j}}}{dt} - CN_{\text{j}} \cdot \frac{dN_{\text{j}}}{dt} \right) \tag{10}\label{eq:cn}
CN_j = \frac{C_j}{N_j}, \qquad j \in \{\text{soil, litter}\}.
$$

$$\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.

### $\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.

Expand All @@ -308,7 +308,7 @@ $$
$$\small i \in \{\text{leaf, wood, fine root, coarse root}\}$$


### $\frak{Litter \ Nitrogen}$
### Litter Nitrogen $N_\text{litter}$

The change in litter nitrogen over time, $N_\text{litter}$ is determined by inputs including leaf and wood litter, nitrogen in organic matter amendments, and losses to mineralization:

Expand All @@ -320,21 +320,26 @@ $$
F^N_\text{litter,min} \tag{13}\label{eq:litter_dndt}
$$

$$\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.

### $\frak{Soil \ Organic \ Nitrogen}$
### Soil Organic Nitrogen $N_\text{org,soil}$

The change in soil nitrogen over time, $N_\text{org,soil}$ is determined by inputs including root loss, litter decomposition, and losses to mineralization:

$$
\frac{dN_\text{org,soil}}{dt} =
\frac{dN_\text{org,soil}}{dt} =
\sum_{i} F^N_i +
F^N_\text{litter} -
F^N_\text{soil,min} \tag{14}\label{eq:org_soil_dndt}
$$

$$\small i \in \{\text{fine root, coarse root}\}$$

The change in nitrogen pools in this model is proportional to the ratio of carbon to nitrogen in the pool. Equations for the evolution of soil and litter CN are below.

### $\frak{Soil \ Mineral \ Nitrogen \ F^N_\text{min}}$
### Soil Mineral Nitrogen $N_\text{min}$

Change in the mineral nitrogen pool over time is determined by inputs from mineralization and fertilization, and losses to volatilization, leaching, and plant uptake:

Expand All @@ -351,7 +356,7 @@ $$

Mineralization and fertilization add to the mineral nitrogen pool. Losses include volatilization, leaching, and plant uptake, described below. Fixed N enters the plant pool directly (Eq. \eqref{eq:n_fix_demand}).

### $\frak{N \ Mineralization \ (F^N_\text{min})}$
### Nitrogen Mineralization $F^N_\text{min}$


Total nitrogen mineralization is proportional to the total heterotrophic respiration from soil and litter pools, divided by the C:N ratio of the pool. The effects of temperature, moisture, tillage, and C:N ratio on mineralization rate are captured in the calculation of $R_\text{H}$.
Expand All @@ -376,7 +381,7 @@ $$
F^N_\mathrm{vol} = K_\text{vol} \cdot N_\text{min} \cdot D_{\text{temp}} \cdot D_{\text{water}R_H} \tag{17}\label{eq:n_vol}
$$

### $\frak{Nitrogen \ Leaching \ F^N_\text{leach}}$
### Nitrogen Leaching $F^N_\text{leach}$

$$
F^N_\text{leach} = N_\text{min} \cdot F^W_{drainage} \cdot f_{N leach} \tag{18}\label{eq:n_leach}
Expand Down
2 changes: 1 addition & 1 deletion docs/user-guide/model-inputs.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

These are the input files needed to run SIPNET:

1. `sipnet.param`: Model parameter file.
1. `sipnet.param`: Model parameter file. See [Parameters](../parameters.md) for full description of this file.
2. `<sitename>.clim`: Climate file, provides weather data for each time step of the simulation period.
3. [optional] `events.in`: Agronomic events.
4. [optional] Run time options (command line arguments or config file `sipnet.in`)
Expand Down
5 changes: 5 additions & 0 deletions src/common/context.c
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,11 @@ void validateContext(void) {
hasError = 1;
}

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.
hasError = 1;
}

if (hasError) {
exit(EXIT_CODE_BAD_PARAMETER_VALUE);
}
Expand Down
38 changes: 30 additions & 8 deletions src/sipnet/events.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ EventNode *createEventNode(int year, int day, int eventType,
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.

switch (eventType) {
case HARVEST: {
Expand Down Expand Up @@ -97,6 +98,12 @@ EventNode *createEventNode(int year, int day, int eventType,
fParams->minN = minN;
// params->nh4_no3_frac = nh4_nos_frac;
newEvent->eventParams = fParams;

if (!ctx.nitrogenCycle && (orgN > 0.0 || minN > 0.0) && !nitrogenWarned) {
logWarning("Fertilization nitrogen quantities are being ignored since "
"nitrogen cycle modeling is off\n");
nitrogenWarned = 1;
}
} break;
case PLANTING: {
double leafC, woodC, fineRootC, coarseRootC;
Expand Down Expand Up @@ -440,9 +447,16 @@ void processEvents(void) {
const double orgC = fertParams->orgC;
const double minN = fertParams->minN;

fluxes.eventOrgN += orgN / climLen;
fluxes.eventLitterC += orgC / climLen;
fluxes.eventMinN += minN / climLen;
if (ctx.nitrogenCycle) {
fluxes.eventOrgN += orgN / climLen;
fluxes.eventMinN += minN / climLen;
} else {
// As the warning says in readEventData(), we ignore N when the
// nitrogen cycle model is off
fluxes.eventOrgN += 0;
fluxes.eventMinN += 0;
Comment on lines +457 to +458
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.
}

writeEventOut(gEvent, 3, "fluxes.eventOrgN", orgN / climLen,
"fluxes.eventLitterC", orgC / climLen, "fluxes.eventMinN",
Expand All @@ -458,26 +472,34 @@ void processEvents(void) {
}

void updatePoolsForEvents(void) {
// CARBON
// Harvest and planting events
envi.plantWoodC += fluxes.eventWoodC * climate->length;
envi.plantLeafC += fluxes.eventLeafC * climate->length;

// Irrigation events
envi.soilWater += fluxes.eventSoilWater * climate->length;

// Harvest and fertilization events
if (ctx.litterPool) {
envi.litterOrgN += fluxes.eventOrgN * climate->length;
envi.litter += fluxes.eventLitterC * climate->length;
} else {
envi.soilOrgN += fluxes.eventOrgN * climate->length;
envi.soil += fluxes.eventLitterC * climate->length;
}
envi.minN += fluxes.eventMinN * climate->length;

// Harvest and planting events
envi.coarseRootC += fluxes.eventCoarseRootC * climate->length;
envi.fineRootC += fluxes.eventFineRootC * climate->length;

// WATER
// Irrigation events
envi.soilWater += fluxes.eventSoilWater * climate->length;

// NITROGEN
// Fertilization events
// (Harvest and planting events TBD)
// Note: litter_pool is required for nitrogen_cycle
if (ctx.nitrogenCycle) {
envi.minN += fluxes.eventMinN * climate->length;
envi.litterOrgN += fluxes.eventOrgN * climate->length;
}
}

void freeEventList(void) {
Expand Down
91 changes: 70 additions & 21 deletions src/sipnet/sipnet.c
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,9 @@ void readParamData(ModelParams **modelParamsPtr, const char *paramFile) {
initializeOneModelParam(modelParams, "litterOrgNInit", &(params.litterOrgNInit), ctx.nitrogenCycle);
initializeOneModelParam(modelParams, "nVolatilizationFrac", &(params.nVolatilizationFrac), ctx.nitrogenCycle);
initializeOneModelParam(modelParams, "nLeachingFrac", &(params.nLeachingFrac), ctx.nitrogenCycle);
initializeOneModelParam(modelParams, "leafCNRatio", &(params.leafCNRatio), ctx.nitrogenCycle);
initializeOneModelParam(modelParams, "woodCNRatio", &(params.woodCNRatio), ctx.nitrogenCycle);
initializeOneModelParam(modelParams, "rootCNRatio", &(params.rootCNRatio), ctx.nitrogenCycle);

// NOLINTEND
// clang-format on
Expand All @@ -409,12 +412,13 @@ void readParamData(ModelParams **modelParamsPtr, const char *paramFile) {
void outputHeader(FILE *out) {
fprintf(out, "Notes: (PlantWoodC, PlantLeafC, Soil and Litter in g C/m^2; "
"Water and Snow in cm; SoilWetness is fraction of WHC;\n");
fprintf(out, "year day time plantWoodC plantLeafC woodCreation ");
fprintf(out, "soil microbeC coarseRootC fineRootC ");
fprintf(out, "litter soilWater soilWetnessFrac snow ");
fprintf(out, "npp nee cumNEE gpp rAboveground rSoil rRoot ra rh rtot "
"evapotranspiration fluxestranspiration minN soilOrgN "
"litterOrgN n2oFlux nLeachFlux\n");
fprintf(out, "year day time plantWoodC plantLeafC woodCreation ");
fprintf(out, "soil microbeC coarseRootC fineRootC ");
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.
"nLeachFlux\n");
}

/*!
Expand All @@ -426,21 +430,22 @@ void outputHeader(FILE *out) {
*/
void outputState(FILE *out, int year, int day, double time) {

fprintf(out, "%4d %3d %5.2f %8.2f %8.2f %8.2f ", year, day, time,
fprintf(out, "%4d %3d %5.2f %10.2f %10.2f %12.2f ", year, day, time,
envi.plantWoodC, envi.plantLeafC, trackers.woodCreation);
fprintf(out, "%8.2f ", envi.soil);
fprintf(out, "%8.2f ", envi.microbeC);
fprintf(out, "%8.2f %8.2f", envi.coarseRootC, envi.fineRootC);
fprintf(out, " %8.2f %8.2f %8.3f %8.2f ", envi.litter, envi.soilWater,
fprintf(out, "%11.2f %9.2f", envi.coarseRootC, envi.fineRootC);
fprintf(out, " %8.2f %9.2f %15.3f %8.2f ", envi.litter, envi.soilWater,
trackers.soilWetnessFrac, envi.snow);
fprintf(out,
"%8.2f %8.2f %8.2f %8.2f %8.3f %8.3f %8.3f %8.3f %8.3f %8.3f %8.8f "
"%8.4f %8.3f %8.4f %8.4f %8.6f %8.4f\n",
trackers.npp, trackers.nee, trackers.totNee, trackers.gpp,
trackers.rAboveground, trackers.rSoil, trackers.rRoot, trackers.ra,
trackers.rh, trackers.rtot, trackers.evapotranspiration,
fluxes.transpiration, envi.minN, envi.soilOrgN, envi.litterOrgN,
fluxes.nVolatilization, fluxes.nLeaching);
fprintf(
out,
"%8.2f %8.2f %8.2f %8.2f %12.3f %8.3f %8.3f %8.3f %8.3f %8.3f %18.8f ",
trackers.npp, trackers.nee, trackers.totNee, trackers.gpp,
trackers.rAboveground, trackers.rSoil, trackers.rRoot, trackers.ra,
trackers.rh, trackers.rtot, trackers.evapotranspiration);
fprintf(out, "%19.4f %8.3f %9.4f %10.4f %9.6f %10.4f\n", fluxes.transpiration,
envi.minN, envi.soilOrgN, envi.litterOrgN, fluxes.nVolatilization,
fluxes.nLeaching);
}

// de-allocate space used for climate linked list
Expand Down Expand Up @@ -1243,6 +1248,33 @@ void calcNLeachingFlux() {
fluxes.nLeaching = envi.minN * phi * params.nLeachingFrac;
}

/**
* Calculate organic nitrogen fluxes
*/
void calcOrgNFluxes() {
double litterCN, soilCN;
// for both litter and soil, mineralization is calculated as heterotrophic
// respiration divided by the C:N ratio of that pool.

// litter
// 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.
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 +
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.
fluxes.fineRootLoss / params.rootCNRatio +
fluxes.coarseRootLoss / params.woodCNRatio;
Comment on lines +1263 to +1275
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.
}

/*!
* Calculate flux terms for sipnet as part of main model flow
*
Expand Down Expand Up @@ -1307,11 +1339,14 @@ void calculateFluxes(void) {
}

// Nitrogen cycle
//
// Many of the nitrogen fluxes depend on carbon flux calculations, so make
// sure this stays at the bottom of this function (or after the carbon calcs,
// at least).
if (ctx.nitrogenCycle) {
calcNVolatilizationFlux();
// Leaching depends on drainage flux so makes sure calcNLeachingFlux
// occurs after calcSoilWaterFluxes
calcNLeachingFlux();
calcOrgNFluxes();
}
}

Expand Down Expand Up @@ -1603,11 +1638,22 @@ void updatePoolsForSoil(void) {
envi.fineRootC +=
(fluxes.fineRootCreation - fluxes.fineRootLoss - fluxes.rFineRoot) *
climate->length;
}

void updateNitrogenPools(void) {
// Nitrogen Cycle
// :: from [5], nitrogen cycle model
// TBD: add equation numbers once published

// Soil mineral N (note we have one mineral pool for soil+litter)
// Mineral N additions from fertilization are handled with the events
envi.minN -= (fluxes.nVolatilization + fluxes.nLeaching) * climate->length;
// envi.soilOrgN += ... TBD
// envi.litterOrgN += ... TBD

// Soil organic N
envi.soilOrgN += fluxes.nOrgSoil * climate->length;

// Litter organic N
envi.litterOrgN += fluxes.nOrgLitter * climate->length;
}

// !!! main runner function !!!
Expand Down Expand Up @@ -1638,6 +1684,9 @@ void updateState(void) {
// Update soil carbon pools
updatePoolsForSoil();

// Update nitrogen cycle pools
updateNitrogenPools();

// Update pools for fluxes from events
updatePoolsForEvents();

Expand Down
Loading