Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ sections to include in release notes:
- `leafon` and `leafoff` events for tracking phenological transitions (#326)
- `leafon` limited by available carbon and nitrogen; N storage pool; N resorption on `leafoff` (#337)
- New required parameter `leafOnReallocFrac` to control how much of wood and coarse root carbon is reallocated to leaves on `leafon` (#337)
- `carbonSaturation` flag and calculation of soil and litter carbon pools that observes soil carbon saturating behavior (#301)

### Fixed

Expand Down
16 changes: 16 additions & 0 deletions docs/model-structure.md
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,13 @@ F^C_\text{fert,org}
Where $K_{\text{plant},i}$ is the turnover rate of plant pool $i$ that controls the rate at which plant biomass is
transferred to litter.

If soil carbon saturation is enabled, the litter carbon pool has an additional input of carbon that was not stabilized for long-term storage by the soil. This functionality is described in more detail below in the Soil Carbon section \eqref{eq:soil_carbon_saturation}.

\begin{equation}
\frac{dC_\text{litter}}{dt} = F^C_\text{litter} + F^C_{\text{soil}} \cdot \frac{C_{\text{soil}}}{f_{\text{C,soil,saturation}}} - F^C_{\text{decomp}} - F^C_{\text{CH}_4\text{,litter}}
\label{eq:soil_carbon_to_litter}
\end{equation}

$F^C_{\text{decomp}}$ represents the rate at which litter carbon is processed by microbial activity. Litter
decomposition
is modeled as a first-order process proportional to litter carbon content and modified by temperature and moisture:
Expand Down Expand Up @@ -370,6 +377,15 @@ F^C_{\text{soil}} = F^C_{\text{soil,litter}} + F^C_{\text{soil,roots}}
\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.


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

\label{eq:soil_carbon_saturation}
\end{equation}

where $f_{\text{C,soil,saturation}}$ is the soil carbon saturation limit entered as an input paramter.

Soil heterotrophic respiration is modeled as a first-order process proportional
to soil organic carbon content and modified by environmental and management factors:

Expand Down
1 change: 1 addition & 0 deletions docs/parameters.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ Run-time parameters can change from one run to the next, or when the model is st
| $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


### Nitrogen Cycle Parameters

Expand Down
1 change: 1 addition & 0 deletions docs/user-guide/model-inputs.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ Thus, command-line arguments override settings in the configuration file, and co
| `snow` | on | Keep track of snowpack, rather than assuming all precipitation is liquid |
| `soil-phenol` | off | Use soil temperature to determine leaf growth |
| `water-hresp` | on | Whether soil moisture affects heterotrophic respiration |
| `carbon-saturation`| off | Enable soil carbon saturation behavior to constrain carbon stored in soil |

Note the following restrictions on these options:
- `soil-phenol` and `gdd` may not both be turned on
Expand Down
2 changes: 2 additions & 0 deletions docs/user-guide/running-sipnet.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ These flags enable or disable optional model processes. Prepend `no-` to the fla
| `--snow` | ON (1) | Track snowpack separately; if disabled, all precipitation is treated as liquid |
| `--soil-phenol` | OFF (0) | Use soil temperature (instead of growing degree days) to determine leaf growth |
| `--water-hresp` | ON (1) | Allow soil moisture to affect heterotrophic respiration rates |
| `--carbon-saturation`| OFF (0) | Enable soil carbon saturation behavior to constrain carbon stored in soil |

#### Model Flag Restrictions

Expand All @@ -56,6 +57,7 @@ The following flag constraints are enforced:
- `--soil-phenol` and `--gdd` cannot both be enabled
- `--anaerobic` requires `--water-hresp`
- `--nitrogen-cycle` requires both `--litter-pool` and `--anaerobic`
- `--carbon-saturation` requires `--litter-pool`

### Output Flags

Expand Down
7 changes: 7 additions & 0 deletions src/common/context.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ void initContext(void) {
CREATE_INT_CONTEXT(nitrogenCycle, "NITROGEN_CYCLE", ARG_OFF, FLAG_YES);
CREATE_INT_CONTEXT(anaerobic, "ANAEROBIC", ARG_OFF, FLAG_YES);
CREATE_INT_CONTEXT(flooding, "FLOODING", ARG_OFF, FLAG_YES);
CREATE_INT_CONTEXT(carbonSaturation,"CARBON_SATURATION",ARG_OFF, FLAG_YES);

// Flags, I/O
CREATE_INT_CONTEXT(doMainOutput, "DO_MAIN_OUTPUT", ARG_ON, FLAG_YES);
Expand Down Expand Up @@ -209,6 +210,12 @@ void validateContext(void) {
hasError = 1;
}

if (ctx.carbonSaturation && !ctx.litterPool) {
logError("carbon-saturation requires litter-pool to be "
"turned on\n");
hasError = 1;
}

if (hasError) {
exit(EXIT_CODE_BAD_PARAMETER_VALUE);
}
Expand Down
4 changes: 2 additions & 2 deletions src/common/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ struct context_metadata {
UT_hash_handle hh; // makes this structure hashable
};

#define NUM_CONTEXT_MODEL_FLAGS 11
#define NUM_CONTEXT_MODEL_FLAGS 12
// See docs/developer-guide/cli-options.md for details on how to add a new
// Context entry
struct Context {
Expand All @@ -53,7 +53,7 @@ struct Context {
int nitrogenCycle;
int anaerobic;
int flooding;

int carbonSaturation;
// IF ADDING A NEW MODEL FLAG, update NUM_CONTEXT_MODEL_FLAGS above and
// relevant code in restart.c

Expand Down
5 changes: 4 additions & 1 deletion src/sipnet/cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ static struct option long_options[] = { // NOLINT
DECLARE_FLAG(nitrogen-cycle),
DECLARE_FLAG(anaerobic),
DECLARE_FLAG(flooding),
DECLARE_FLAG(carbon-saturation),

DECLARE_FLAG(do-main-output),
DECLARE_FLAG(do-single-outputs),
Expand Down Expand Up @@ -65,7 +66,7 @@ char *argNameMap[] = {
DECLARE_ARG_FOR_MAP(litterPool), DECLARE_ARG_FOR_MAP(snow),
DECLARE_ARG_FOR_MAP(soilPhenol), DECLARE_ARG_FOR_MAP(waterHResp),
DECLARE_ARG_FOR_MAP(nitrogenCycle), DECLARE_ARG_FOR_MAP(anaerobic),
DECLARE_ARG_FOR_MAP(flooding),
DECLARE_ARG_FOR_MAP(flooding), DECLARE_ARG_FOR_MAP(carbonSaturation),

// I/O
DECLARE_ARG_FOR_MAP(doMainOutput), DECLARE_ARG_FOR_MAP(doSingleOutputs),
Expand Down Expand Up @@ -98,6 +99,7 @@ void usage(char *progName) {
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

printf("\n");
printf("Output flags: (prepend flag with 'no-' to force off, eg '--no-print-header')\n");
printf(" --do-main-output Print time series of all output variables to <file-prefix>.out (1)\n");
Expand All @@ -121,6 +123,7 @@ void usage(char *progName) {
printf(" --soil-phenol and --gdd may not both be turned on\n");
printf(" --anaerobic requires --water-hresp\n");
printf(" --nitrogen-cycle requires both --litter-pool and --anaerobic\n");
printf(" --carbon-saturation requires --litter-pool\n");
// clang-format on
}

Expand Down
2 changes: 1 addition & 1 deletion src/sipnet/cli.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

// The run-time option names do not match their corresponding fields in Context,
// so we need a way to get from one to the other.
#define NUM_FLAG_OPTIONS 16
#define NUM_FLAG_OPTIONS 17
extern char *argNameMap[2 * NUM_FLAG_OPTIONS];

/*!
Expand Down
7 changes: 6 additions & 1 deletion src/sipnet/restart.c
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ typedef struct RestartContextModelFlags {
int nitrogenCycle;
int anaerobic;
int flooding;
int carbonSaturation;
} RestartContextModelFlags;
static RestartContextModelFlags modelFlags;

Expand Down Expand Up @@ -162,7 +163,8 @@ void initResetState(RestartState *state, MeanTracker *npp) {
state->flagsPF[8] = (StateField){"flags.nitrogenCycle", FT_INT, &modelFlags.nitrogenCycle, 0};
state->flagsPF[9] = (StateField){"flags.anaerobic", FT_INT, &modelFlags.anaerobic, 0};
state->flagsPF[10] = (StateField){"flags.flooding", FT_INT, &modelFlags.flooding, 0};
state->flagsPF[11] = (StateField){"flags.invalid", FT_INVALID, NULL, FIELD_INVALID};
state->flagsPF[11] = (StateField){"flags.carbonSaturation", FT_INT, &modelFlags.carbonSaturation, 0};
state->flagsPF[12] = (StateField){"flags.invalid", FT_INVALID, NULL, FIELD_INVALID};

state->boundaryPF[0] = (StateField){"boundary.year", FT_INT, &boundaryClimate.year, 0};
state->boundaryPF[1] = (StateField){"boundary.day", FT_INT, &boundaryClimate.day, 0};
Expand Down Expand Up @@ -777,6 +779,7 @@ static void checkRestartContextCompatibility(void) {
mismatch |= (ctx.nitrogenCycle != modelFlags.nitrogenCycle);
mismatch |= (ctx.anaerobic != modelFlags.anaerobic);
mismatch |= (ctx.flooding != modelFlags.flooding);
mismatch |= (ctx.carbonSaturation != modelFlags.carbonSaturation);

if (mismatch) {
logError("Restart context mismatch: model flags must match checkpoint "
Expand Down Expand Up @@ -900,6 +903,8 @@ void restartWriteCheckpoint(const char *restartOut, MeanTracker *meanNPP) {
++numFlagsSet;
modelFlags.flooding = ctx.flooding;
++numFlagsSet;
modelFlags.carbonSaturation = ctx.carbonSaturation;
++numFlagsSet;
if (numFlagsSet != NUM_CONTEXT_MODEL_FLAGS) {
logInternalError("Not all model flags set while writing checkpoint\n");
exit(EXIT_CODE_INTERNAL_ERROR);
Expand Down
35 changes: 26 additions & 9 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) {
// Water drainage
initializeOneModelParam(modelParams, "waterDrainFrac", &(params.waterDrainFrac), ctx.flooding);

// Soil carbon saturation
initializeOneModelParam(modelParams, "soilCSaturation", &(params.soilCSaturation), ctx.carbonSaturation);

// NOLINTEND
// clang-format on

Expand Down Expand Up @@ -1647,16 +1650,30 @@ void updateMainPools(void) {
*/
void updatePoolsForSoil(void) {
if (ctx.litterPool) {
// :: from [2], litter model description
envi.litterC +=
(fluxes.woodLitter + fluxes.leafLitter - fluxes.litterToSoil -
fluxes.rLitter - fluxes.litterMethane) *
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.

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;

// from [2] and [3], litter and root terms respectively
envi.soilC += (fluxes.coarseRootLoss + fluxes.fineRootLoss +
fluxes.litterToSoil - fluxes.rSoil - fluxes.soilMethane) *
climate->length;
envi.soilC += (soilInputs * (1 - saturationFraction) - fluxes.rSoil -
fluxes.soilMethane) *
climate->length;
Comment on lines +1654 to +1665
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 on lines +1653 to +1665
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

} else {
// :: from [2], litter model description
envi.litterC +=
(fluxes.woodLitter + fluxes.leafLitter - fluxes.litterToSoil -
fluxes.rLitter - fluxes.litterMethane) *
climate->length;
// from [2] and [3], litter and root terms respectively
envi.soilC += (fluxes.coarseRootLoss + fluxes.fineRootLoss +
fluxes.litterToSoil - fluxes.rSoil - fluxes.soilMethane) *
climate->length;
}
} else {
// Normal pool (single pool, no microbes)
// :: from [1] (and others, TBD), eq (A3), where:
Expand Down
9 changes: 9 additions & 0 deletions src/sipnet/state.h
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,14 @@ typedef struct Parameters {

// Relative methane production rate in the litter pool, in [0, 1), per day
double litterMethaneRate;

// ******
// Soil carbon saturation
// ******

// Maximum threshold for stabilizing carbon in soil organic pool as
// slow-turnover pool units: g C * m^-2 ground area
double soilCSaturation;
} Params;

#define NUM_PARAMS (sizeof(Params) / sizeof(double))
Expand Down Expand Up @@ -628,6 +636,7 @@ typedef struct FluxVars {
double soilMethane;
// Methane produced from litter
double litterMethane;

} Fluxes;

// Global var
Expand Down
2 changes: 1 addition & 1 deletion tests/sipnet/test_modeling/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ LDFLAGS=-L$(ROOT_DIR)/libs
LDLIBS=-lsipnet -lsipnet_common -lm

# List test files in this directory here
TEST_CFILES=testNitrogenCycle.c testDependencyFunctions.c testBalance.c testMethane.c testSoilMoisture.c
TEST_CFILES=testNitrogenCycle.c testDependencyFunctions.c testBalance.c testMethane.c testSoilMoisture.c testCarbonSaturation.c

# The rest is boilerplate, likely copyable as is to a new test directory
TEST_OBJ_FILES=$(TEST_CFILES:%.c=%.o)
Expand Down
Loading
Loading