Skip to content

Commit c7750f0

Browse files
Alomirdlebauer
andauthored
SIP333 Refactor Nitrogen (#334)
* Interim commit * First pass at refactor; N limit still needs work * First pass at refactor; N limit still needs work * Tweaks before N limit refactor * Fix CN calcs * Update for refactored N limitation * Refactor N limitation to limitations.c * Doc update * SIP333 docs (#335) * update model structure b/c not all source is in sipnet.c * added check limitations to code-structure doc * Signature and scope tweaks from PR feedback --------- Co-authored-by: David LeBauer <dlebauer@gmail.com>
1 parent e886922 commit c7750f0

15 files changed

Lines changed: 564 additions & 403 deletions

File tree

CMakeLists.txt

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,11 @@ add_library(commonlib
3232
add_library(sipnetlib
3333
src/sipnet/balance.c
3434
src/sipnet/cli.c
35+
src/sipnet/depeffects.c
3536
src/sipnet/events.c
3637
src/sipnet/frontend.c
38+
src/sipnet/limitations.c
39+
src/sipnet/nitrogen.c
3740
src/sipnet/outputItems.c
3841
src/sipnet/restart.c
3942
src/sipnet/runmean.c
@@ -42,25 +45,28 @@ add_library(sipnetlib
4245
)
4346

4447
add_library(tests
45-
tests/sipnet/test_sipnet_infrastructure/testParamInput.c
46-
tests/sipnet/test_sipnet_infrastructure/testClimInput.c
48+
tests/sipnet/test_bugfixes/testEventFileOrderChecks.c
4749
tests/sipnet/test_events_infrastructure/testEventInfra.c
48-
tests/sipnet/test_events_infrastructure/testEventOutputFile.c
4950
tests/sipnet/test_events_infrastructure/testEventInfraNeg.c
50-
tests/sipnet/test_events_types/testEventPlanting.c
51-
tests/sipnet/test_events_types/testEventIrrigation.c
51+
tests/sipnet/test_events_infrastructure/testEventOutputFile.c
5252
tests/sipnet/test_events_types/testEventFertilization.c
5353
tests/sipnet/test_events_types/testEventHarvest.c
54+
tests/sipnet/test_events_types/testEventIrrigation.c
55+
tests/sipnet/test_events_types/testEventLeafOnOff.c
56+
tests/sipnet/test_events_types/testEventPlanting.c
5457
tests/sipnet/test_events_types/testEventTillage.c
55-
tests/sipnet/test_bugfixes/testEventFileOrderChecks.c
56-
tests/sipnet/test_modeling/testNitrogenCycle.c
57-
tests/sipnet/test_modeling/testDependencyFunctions.c
5858
tests/sipnet/test_modeling/testBalance.c
59+
tests/sipnet/test_modeling/testDependencyFunctions.c
60+
tests/sipnet/test_modeling/testMethane.c
61+
tests/sipnet/test_modeling/testNitrogenCycle.c
5962
tests/sipnet/test_modeling/testSoilMoisture.c
63+
tests/sipnet/test_restart_infrastructure/mock_state.c
6064
tests/sipnet/test_restart_infrastructure/testRestartMVP.c
61-
tests/sipnet/test_restart_infrastructure/testRestartMissedEnvi.c
6265
tests/sipnet/test_restart_infrastructure/testRestartMissedCtx.c
63-
tests/sipnet/test_restart_infrastructure/mock_state.c
64-
tests/sipnet/test_restart_infrastructure/bad_code/ctx_fail.c
65-
tests/sipnet/test_restart_infrastructure/bad_code/envi_fail.c
66+
tests/sipnet/test_restart_infrastructure/testRestartMissedEnvi.c
67+
tests/sipnet/test_sipnet_infrastructure/testClimInput.c
68+
tests/sipnet/test_sipnet_infrastructure/testOutputHeader.c
69+
tests/sipnet/test_sipnet_infrastructure/testParamInput.c
70+
tests/utils/helpers.c
71+
tests/utils/exitHandler.c
6672
)

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ COMMON_CFILES:=context.c logging.c modelParams.c util.c
1111
COMMON_CFILES:=$(addprefix src/common/, $(COMMON_CFILES))
1212
COMMON_OFILES=$(COMMON_CFILES:.c=.o)
1313

14-
SIPNET_CFILES:=sipnet.c cli.c events.c frontend.c outputItems.c restart.c runmean.c state.c balance.c
14+
SIPNET_CFILES:=sipnet.c cli.c depeffects.c events.c frontend.c limitations.c nitrogen.c outputItems.c restart.c runmean.c state.c balance.c
1515
SIPNET_CFILES:=$(addprefix src/sipnet/, $(SIPNET_CFILES))
1616
SIPNET_OFILES=$(SIPNET_CFILES:.c=.o)
1717
SIPNET_LIBS=-lsipnet_common

docs/developer-guide/code-structure.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@ This guide documents how state is advanced each timestep and the conventions tha
88
- Zero all `fluxes.*` and `fluxes.event*`.
99

1010
2) Compute fluxes (pure calculations)
11-
- `calculateFluxes()` computes photosynthesis, respiration, water/snow, etc.
1211
- `processEvents()` converts scheduled/instant events to `fluxes.event*` deltas (no pool mutation).
13-
- No function in this phase mutates `envi.*` or `trackers.*`.
14-
12+
- `calculateFluxes()` computes photosynthesis, respiration, water/snow, etc.
13+
- `checkLimitations()` may adjust already-computed fluxes when a flux is resource limited.
14+
- No function in this phase mutates `envi.*` or `trackers.*`.
1515

1616
3) Apply pool updates (single place)
1717
- `updateMainPools()` updates leafC, woodC, soil water and snow pools
1818
- `updatePoolsForSoil()` updates soil carbon pools
19+
- `updateNitrogenPools()` updates mineral, soil organic, and litter nitrogen pools.
1920
- `updatePoolsForEvents()` updates pools for fluxes from events
2021
- The above functions are the only code that changes `envi.*`.
2122
- For each pool P: ΔP = ((sum of rate fluxes to P) + (sum of event fluxes to P)) * climate.length

docs/model-structure.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ This document provides an overview of the SIPNET model’s structure. It was wri
2424
- Focus on features currently in regular use.
2525

2626
There are multiple ways to configure the model structure, and not all model structures or components are listed.
27-
Implementation in source code (sipnet.c) is annotated with references to specific publications.
27+
Implementation in source code is annotated with references to specific publications.
2828

2929
#### Notes on notation:
3030

@@ -1174,7 +1174,7 @@ nitrogen (calculated from the leaf C:N ratio) is also transferred to the litter
11741174
**Event parameters:**
11751175

11761176
| Parameter | Value | Description |
1177-
|-----------|----------------------|-------------------|
1177+
| --------- | -------------------- | ----------------- |
11781178
| Year | integer | Year |
11791179
| Day | integer | Day of year |
11801180
| Type | `leafon` / `leafoff` | The type of event |

src/common/util.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,8 @@ int countFields(const char *line, const char *sep) {
6868
free(lineCopy);
6969
return numParams;
7070
}
71+
72+
double calcRatio(const double num, const double den) {
73+
const double effectiveDen = den < TINY ? TINY : den;
74+
return num / effectiveDen;
75+
}

src/common/util.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
#include <stdio.h>
1212

13+
#define TINY 0.000001 // to avoid those nasty divide-by-zero errors
14+
1315
// our own openFile method, which exits gracefully if there's an error
1416
FILE *openFile(const char *name, const char *mode);
1517

@@ -21,4 +23,9 @@ int stripComment(char *line, const char *commentChars);
2123

2224
int countFields(const char *line, const char *sep);
2325

26+
/**
27+
* Calculate the ratio of the inputs safely
28+
*/
29+
double calcRatio(double num, double den);
30+
2431
#endif

src/sipnet/depeffects.c

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#include "depeffects.h"
2+
3+
#include <math.h>
4+
5+
#include "common/context.h"
6+
7+
#include "events.h"
8+
#include "state.h"
9+
10+
double getClippedWaterFrac(double water, double whc) {
11+
return fmin(fmax(water / whc, 0.0), 1.0);
12+
}
13+
14+
double calcAnaerobicIndex(double water, double whc) {
15+
double f_whc = getClippedWaterFrac(water, whc);
16+
double f_a = params.fAnoxia;
17+
18+
// Anaerobic index (oxygen limitation proxy)
19+
return fmin(fmax((f_whc - f_a) / (1 - f_a), 0), 1);
20+
}
21+
22+
double calcRespMoistEffect(double water, double whc) {
23+
double moistEffect;
24+
25+
if ((!ctx.waterHResp) || (climate->tsoil < 0)) {
26+
// if not waterHResp, soil moisture does not affect heterotrophic
27+
// respiration; also, ignore moisture effects in frozen soils
28+
// :: from [2], snowpack addition
29+
moistEffect = 1.0;
30+
} else {
31+
double f_whc = getClippedWaterFrac(water, whc);
32+
if (!ctx.anaerobic) {
33+
// :: from [1], first part of eq (A20), with added exponent
34+
// Original formulation from [1], based on PnET is:
35+
// moistEffect = water / whc
36+
// which matches here with params.soilRespMoistEffect=1 (the default
37+
// value)
38+
//
39+
// [TAG:UNKNOWN_PROVENANCE] soilRespMoistEffect
40+
// Note: older versions of sipnet note this as "using PnET formulation",
41+
// but we have been unable to verify that the exponent comes from PnET
42+
moistEffect = pow(f_whc, params.soilRespMoistEffect);
43+
} else {
44+
// Unimodal moisture response: suppressed under dry conditions, maximal
45+
// at intermediate moisture, reduced under saturated/anoxic conditions
46+
47+
// Aerobic water availability (dry limitation)
48+
double D_aer = fmin(fmax(f_whc / params.fAnoxia, 0), 1);
49+
// Anaerobic index (oxygen limitation proxy)
50+
double A = calcAnaerobicIndex(water, whc);
51+
// Uni-modal moisture response
52+
// Note that this will not go above 1, since:
53+
// - when f_whc <= f_a, A = 0, and moistEffect = D_aer
54+
// - when f_whc > f_a, D_aer caps at 1, and this becomes
55+
// moistEffect = 1 - (1 - adr)A, and adr <= 1
56+
moistEffect = (1 - A) * D_aer + params.anaerobicDecompRate * A;
57+
}
58+
}
59+
60+
return moistEffect;
61+
}
62+
63+
double calcMethaneMoistEffect(double water, double whc) {
64+
// Anaerobic index (oxygen limitation proxy)
65+
double A = calcAnaerobicIndex(water, whc);
66+
67+
return pow(A, params.anaerobicTransExp);
68+
}
69+
70+
double calcTempEffect(double tsoil) {
71+
// :: from [1], D_temp calc as part of eq (A20)
72+
return pow(params.soilRespQ10, tsoil / 10);
73+
}
74+
75+
double calcTillageEffect(void) { return 1 + eventTrackers.d_till_mod; }
76+
77+
double calcCNEffect(double kCN, double poolC, double poolN) {
78+
if (!ctx.nitrogenCycle) {
79+
return 1.0;
80+
}
81+
82+
// CN ratio dependency term, using k_CN term that indicates CN value
83+
// at which the dependency is 1/2
84+
double cn = calcRatio(poolC, poolN);
85+
return kCN / (kCN + cn);
86+
}
87+
88+
double calcVolatilizationMoistEffect(double water, double whc) {
89+
// Anaerobic index (oxygen limitation proxy)
90+
double A = calcAnaerobicIndex(water, whc);
91+
92+
// 0.05 represents the baseline aerobic volatilization
93+
// The factor of 3.8 makes the max = 1, as with other dependency functions
94+
return 0.05 + 3.8 * A * (1 - A);
95+
}

src/sipnet/depeffects.h

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
#ifndef DEP_EFFECTS_H
2+
#define DEP_EFFECTS_H
3+
4+
/**
5+
* Calculate water fraction bounded in [0,1]
6+
*
7+
* Keeps moisture dependency terms bounded in [0, 1]. In configurations with
8+
* WATER_HRESP=1 and ANAEROBIC=0 (e.g., russell_1 smoke test), this prevents
9+
* super-saturated soil water (water > whc) from boosting respiration above
10+
* the full-wet response. Also used to constrain water frac for evapotrans
11+
* calcs.
12+
*
13+
* @param water current water level
14+
* @param whc water holding capacity
15+
* @return bounded water fraction
16+
*/
17+
double getClippedWaterFrac(double water, double whc);
18+
19+
/**
20+
* Calculate the anaerobic index
21+
*
22+
* Calculate the index from current soil moisture, soil WHC, and the
23+
* fAnoxia parameter
24+
*
25+
* @param water current soil water
26+
* @param whc soil water holding capacity
27+
* @return anaerobic index
28+
*/
29+
double calcAnaerobicIndex(double water, double whc);
30+
31+
/*!
32+
* Calculate heterotrophic respiration moisture dependency effect
33+
*
34+
* This dependency term is used in soil respiration and litter breakdown
35+
*
36+
* Calculation also depends on soil temperature and the options waterHResp and
37+
* anaerobic.
38+
*
39+
* @param water current soil water
40+
* @param whc water holding capacity
41+
* @return moisture effect as a fraction between 0 and 1
42+
*/
43+
double calcRespMoistEffect(double water, double whc);
44+
45+
/*!
46+
* Calculate methane production moisture dependency effect
47+
*
48+
* Methane production increases monotonically with anoxia, so we calculate
49+
* this dependency term as power of the anaerobic index.
50+
*
51+
* This dependency term is used in methane production
52+
*
53+
* Calculation also depends on soil temperature.
54+
*
55+
* @param water current soil water
56+
* @param whc water holding capacity
57+
* @return moisture effect as a fraction between 0 and 1
58+
*/
59+
double calcMethaneMoistEffect(double water, double whc);
60+
61+
/**
62+
* Calculate temperature dependency effect
63+
*
64+
* Temperature dependency term is used in soil respiration, litter
65+
* breakdown, and nitrogen volatilization.
66+
*
67+
* @param tsoil soil temperature
68+
* @return temperature effect as a fraction between 0 and 1
69+
*/
70+
double calcTempEffect(double tsoil);
71+
72+
/**
73+
* Calculate effect of tillage on soil respiration or litter breakdown
74+
*/
75+
double calcTillageEffect(void);
76+
77+
/**
78+
* Calculate C:N ratio dependency effect
79+
*
80+
* C:N ratio dependency term is used in soil respiration and litter
81+
* breakdown.
82+
*
83+
* @param kCN CN dependency control param for soil/litter
84+
* @param poolC Current size of carbon pool
85+
* @param poolN Current size of nitrogen pool
86+
* @return C:N ratio effect as a fraction between 0 and 1
87+
*/
88+
double calcCNEffect(double kCN, double poolC, double poolN);
89+
90+
/*!
91+
* Calculate volatilization moisture dependency effect
92+
*
93+
* The volatilized nitrogen flux is assumed to peak at intermediate redox
94+
* conditions, where aerobic and anaerobic processes overlap.
95+
*
96+
* This dependency term is used in nitrogen volatilization
97+
*
98+
* Calculation also depends on soil temperature.
99+
*
100+
* @param water current soil water
101+
* @param whc water holding capacity
102+
* @return moisture effect as a fraction between 0 and 1
103+
*/
104+
double calcVolatilizationMoistEffect(double water, double whc);
105+
106+
#endif // DEP_EFFECTS_H

src/sipnet/limitations.c

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#include "limitations.h"
2+
3+
#include <math.h>
4+
5+
#include "common/context.h"
6+
#include "common/logging.h"
7+
#include "common/util.h"
8+
9+
#include "nitrogen.h"
10+
#include "state.h"
11+
12+
/**
13+
* Check for nitrogen limitation, and reduce growth if needed
14+
*/
15+
static void checkNitrogenLimitation(void) {
16+
// First, determine if we are in a nitrogen-limited situation
17+
double maxDemandFlux = calcPlantNDemand();
18+
double maxDemand = maxDemandFlux * climate->length;
19+
20+
double nonUptakeFluxes = calcMinNNonUptakeFluxes();
21+
double availableMinN =
22+
fmax(0, envi.minN + (nonUptakeFluxes * climate->length));
23+
24+
double nFixationFrac = calcNFixationFrac();
25+
double maxUptake = maxDemand * (1 - nFixationFrac);
26+
27+
if (maxUptake > TINY && maxUptake > availableMinN) {
28+
// More demand than supply - N limitation is in effect
29+
double reduction = availableMinN / maxUptake;
30+
logInfo("N uptake %.4f exceeds available soil min N %.4f, reducing plant "
31+
"growth by %.2f%% on year %d day %d time %.3f\n",
32+
maxUptake, availableMinN, (1 - reduction) * 100, climate->year,
33+
climate->day, climate->time);
34+
35+
// Reduce all drains on soil N
36+
fluxes.eventLeafOnCreation *= reduction;
37+
fluxes.leafOnCreation *= reduction;
38+
fluxes.woodCreation *= reduction;
39+
fluxes.leafCreation *= reduction;
40+
fluxes.fineRootCreation *= reduction;
41+
fluxes.coarseRootCreation *= reduction;
42+
fluxes.nFixation *= reduction;
43+
fluxes.nUptake *= reduction;
44+
}
45+
}
46+
47+
// See limitations.h
48+
void checkLimitations(void) {
49+
// EXPECTED: calcLeafOnLimitation()
50+
51+
if (ctx.nitrogenCycle) {
52+
checkNitrogenLimitation();
53+
}
54+
}

src/sipnet/limitations.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#ifndef SIPNET_LIMITATIONS_H
2+
#define SIPNET_LIMITATIONS_H
3+
4+
/**
5+
* Check for complex limitations
6+
*
7+
* Check for complex situations:
8+
* - leaf-on limitation
9+
* - nitrogen limitation
10+
*/
11+
void checkLimitations(void);
12+
13+
#endif // SIPNET_LIMITATIONS_H

0 commit comments

Comments
 (0)