Skip to content

SIP287 Add C Saturation#301

Open
mswilburn wants to merge 14 commits into
masterfrom
SIP287-Implement-Carbon-Saturation
Open

SIP287 Add C Saturation#301
mswilburn wants to merge 14 commits into
masterfrom
SIP287-Implement-Carbon-Saturation

Conversation

@mswilburn
Copy link
Copy Markdown
Contributor

@mswilburn mswilburn commented Mar 27, 2026

Summary

Implementing soil carbon saturation by creating a flag, adding a new parameter, modifying fluxes into soil organic carbon pool, adding new flux from soil organic carbon pool to litter pool, and updating documentation. Motivation for this change includes evaluating model soil carbon dynamics with and without an explicit maximum limit for long-term storage.

How was this change tested?

List steps taken to test this change, with appropriate outputs if applicable

If changes are needed for any sipnet.out files in the test/smoke subdirectories, then include output from
tools/smoke_check.py by running the commands below and pasting the output at the end of this PR. Note that
this must be run BEFORE submitting changes to any sipnet.out files.

make smoke
python tools/smoke_check.py run verbose <list tests/smoke subdirectories with changed outputs>

Run python tools/smoke_check.py help for more info.

Related issues

Checklist

  • Related issues are listed above. PRs without an approved, related issue may not get reviewed.
  • PR title has the issue number in it ("[#] <concise description of proposed change>")
  • Tests added/updated for new features (if applicable)
  • Documentation updated (if applicable)
  • docs/CHANGELOG.md updated with noteworthy changes
  • Code formatted with clang-format (run git clang-format if needed)

Note: See CONTRIBUTING.md for additional guidance. This repository uses automated formatting checks; if the pre-commit hook blocks your commit, run git clang-format to format staged changes.

Copy link
Copy Markdown
Collaborator

@Alomir Alomir left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New flag option looks good! (Other than the one issue in restart.c)

Comment thread src/sipnet/restart.c Outdated
Comment on lines 164 to 165
state->flagsPF[8] = (StateField){"flags.carbonSaturation", FT_INT, &modelFlags.carbonSaturation, 0};
state->flagsPF[10] = (StateField){"flags.invalid", FT_INVALID, NULL, FIELD_INVALID};
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think these should be flagsPF[10] for carbonSat and flagsPF[11] for invalid

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, thanks for catching that. Fixed!

@mswilburn
Copy link
Copy Markdown
Contributor Author

Smoke and Unit test output.

PR301 Smoke Test Output.txt
PR301 Unit Test Output.txt

@mswilburn mswilburn marked this pull request as ready for review May 22, 2026 12:43
@Alomir Alomir requested a review from Copilot May 27, 2026 16:34
Copy link
Copy Markdown
Contributor

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

Implements optional soil carbon saturation (issue #287) by adding a new carbonSaturation model flag, a soilCSaturation parameter, and a saturation-fraction-based rerouting of root-loss and litter-to-soil carbon back into the litter pool in updatePoolsForSoil(). The flag is wired through Context, CLI, restart serialization, and validation (it requires litterPool), and is exercised by a new unit test and existing smoke runs.

Changes:

  • Adds carbon-saturation CLI/model flag (Context, CLI map, restart schema, validation) and registers a new optional soilCSaturation parameter.
  • Modifies updatePoolsForSoil() to partition soil-bound carbon inputs by envi.soilC / params.soilCSaturation between soil and litter pools when the flag is on.
  • Adds testCarbonSaturation.c unit test plus smoke-test config/param updates.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/sipnet/sipnet.c New saturation branch in updatePoolsForSoil() and parameter registration.
src/sipnet/state.h Adds soilCSaturation param field (and a stray blank line in FluxVars).
src/sipnet/cli.c, cli.h Adds --carbon-saturation flag, arg map entry, usage text, bumps NUM_FLAG_OPTIONS.
src/common/context.h, context.c Adds carbonSaturation Context field, initializer, and validateContext() dependency on litterPool.
src/sipnet/restart.c Adds the flag to restart schema, serialization, and compatibility check.
tests/sipnet/test_modeling/testCarbonSaturation.c, Makefile New unit test covering four input/saturation combinations.
tests/smoke/{niwot,russell_*}/sipnet.config, sipnet.param Adds default-off CARBON_SATURATION line and soilCSaturation 1.0 param entry.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/sipnet/sipnet.c
envi.soilC += (fluxes.coarseRootLoss + fluxes.fineRootLoss +
fluxes.litterToSoil - fluxes.rSoil - fluxes.soilMethane) *
climate->length;
if (ctx.carbonSaturation) {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should make sure this is covered in #278

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Alomir If parameter validation is happening in another PR, do I need to do anything with it here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a comment to address more broadly in #278 per conversation with @Alomir.

Comment thread src/sipnet/sipnet.c
Comment on lines +1604 to +1615
double saturationFraction = envi.soilC / params.soilCSaturation;
double soilInputs =
fluxes.coarseRootLoss + fluxes.fineRootLoss + fluxes.litterToSoil;

envi.litterC += (fluxes.woodLitter + fluxes.leafLitter +
(soilInputs * saturationFraction) - fluxes.litterToSoil -
fluxes.rLitter - fluxes.litterMethane) *
climate->length;

envi.soilC += (soilInputs * (1 - saturationFraction) - fluxes.rSoil -
fluxes.soilMethane) *
climate->length;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sounds like something to address

Comment thread src/sipnet/sipnet.c
Comment on lines +1603 to +1615
if (ctx.carbonSaturation) {
double saturationFraction = envi.soilC / params.soilCSaturation;
double soilInputs =
fluxes.coarseRootLoss + fluxes.fineRootLoss + fluxes.litterToSoil;

envi.litterC += (fluxes.woodLitter + fluxes.leafLitter +
(soilInputs * saturationFraction) - fluxes.litterToSoil -
fluxes.rLitter - fluxes.litterMethane) *
climate->length;

envi.soilC += (soilInputs * (1 - saturationFraction) - fluxes.rSoil -
fluxes.soilMethane) *
climate->length;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this definitely needs to be addressed

Comment on lines +79 to +94
soilInputs =
fluxes.coarseRootLoss + fluxes.fineRootLoss + fluxes.litterToSoil;
saturationFraction = expSoilC / params.soilCSaturation;

expLitterC += (fluxes.woodLitter + fluxes.leafLitter +
(soilInputs * saturationFraction) - fluxes.litterToSoil -
fluxes.rLitter - fluxes.litterMethane) *
climate->length;

expSoilC += (soilInputs * (1 - saturationFraction) - fluxes.rSoil -
fluxes.soilMethane) *
climate->length;

updatePoolsForSoil();
status |= checkSoil(expSoilC);
status |= checkLitter(expLitterC);
Comment thread src/sipnet/cli.c
printf(" --snow Keep track of snowpack, rather than assuming all precipitation is liquid (1)\n");
printf(" --soil-phenol Use soil temperature to determine leaf growth (0)\n");
printf(" --water-hresp Whether soil moisture affects heterotrophic respiration (1)\n");
printf(" --carbon-saturation Enable maximum storage limit of soil organic carbon (0)\n");
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated documentation in all listed files

Comment thread src/sipnet/state.h
double soilMethane;
// Methane produced from litter
double litterMethane;

Copy link
Copy Markdown
Member

@dlebauer dlebauer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great, glad to have this feature and look forward to seeing whether and when it improves model performance.

I'm about to start a code review but before merging, could you please update the documentation?

Copy link
Copy Markdown
Member

@dlebauer dlebauer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding the docs. I made a few comments related to improving clarity.

Adding the clamp to the code as requested by Mike and Copilot will make code consistent with docs, and avoid negative fluxes.

Comment thread docs/model-structure.md
\label{eq:soil_carbon_flux}
\end{equation}

If soil carbon saturation is enabled, only a fraction of the total carbon input will be added to the soil. The amount added to the soil carbon pool is a function of the proximity of the pool to the soil carbon saturation limit. As the soil carbon pool aqcuires more carbon and approaches its saturation limit, the amount of carbon stored in the pool decreases. This represents the physical and chemical constraints of the mineralogical surfaces of soil to stabilize carbon for long-term storage. The remaining carbon that is not stabilized in the soil carbon pool due to saturation constraints will be returned to the litter carbon pool to act as fast-turnover carbon \eqref{eq:soil_carbon_to_litter}.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This paragraph could be tightened for clarity and precision.

In particular , the explanation beginning with “This represents the physical and chemical constraints…” is more mechanistic than the implementation. This would be useful context in a manuscript introduction or discussion, but IMHO is TMI here. At the same time, it would be useful to cite one or more papers if they provide the formulation implemented here.

Possible revision:

When soil carbon saturation is enabled, only a saturation-dependent fraction of gross soil C inputs is added to the soil pool. This fraction declines as (C_{\text{soil}}) approaches the specified soil C saturation limit. The remaining input C is diverted[?] to the litter pool as fast-turnover carbon rather than being added to the soil pool.

[?]I'm not sure if diverted is correct here. But 'returned' also seems incorrect- if the total flux to soil comes from litter + roots

Goal is to align docs with the implemented structure. More detailed explanation, if included, should clarify something like 'this formula represents [mechanism] described in [citation].'

And if equations do come from another source then the source should be cited.

Comment thread docs/parameters.md
| $Q_{10s}$ | soilRespQ10 | Soil respiration Q10 | unitless | scalar determining effect of temp on soil respiration |
| $D_{\text{moisture}}$ | soilRespMoistEffect | scalar determining effect of moisture on soil resp. | unitless | |
| $f_{\text{till}}$ | tillageEff | Effect of tillage on decomposition that exponentially decays over time | fraction | Documented in model structure; event-level term in `events.in` |
| $f_{\text{C,soil,saturation}}$ | soilCSaturation | Maximum amount of carbon that can be stabilized in the soil determined by soil texture | $\text{g C} \cdot \text{m}^{-2} \text{ ground area}$ | |
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

f is reserved for unitless "fraction" parameters.

Something like C_soil,sat, or C^*_soil

Comment thread docs/model-structure.md
If soil carbon saturation is enabled, only a fraction of the total carbon input will be added to the soil. The amount added to the soil carbon pool is a function of the proximity of the pool to the soil carbon saturation limit. As the soil carbon pool aqcuires more carbon and approaches its saturation limit, the amount of carbon stored in the pool decreases. This represents the physical and chemical constraints of the mineralogical surfaces of soil to stabilize carbon for long-term storage. The remaining carbon that is not stabilized in the soil carbon pool due to saturation constraints will be returned to the litter carbon pool to act as fast-turnover carbon \eqref{eq:soil_carbon_to_litter}.

\begin{equation}
\frac{dC_\text{soil}}{dt} = F^C_{\text{soil}} \cdot (1 - \frac{C_{\text{soil}}}{f_{\text{C,soil,saturation}}}) - R_{\text{soil}} - F^C_{\text{CH}_4\text{,soil}}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

F^C_{\text{soil}} it is used earlier to represent “total carbon input to soil pool,” not as an unstabilized return flux, please confirm that it is the correct quantity to use here.

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.

Implement Carbon Saturation in SIPNET

4 participants