Skip to content

Commit e7017ca

Browse files
Alomirdlebauer
andauthored
SIP128 Add fert event handling (C only) (#131)
* Interim commit * Updates for fert event, harvest fix, and tests * Updates for fert event, harvest fix, and tests * Updates testEventOutputFile after harvest changes * updates test output for consistency * Adds test for fertilization event * Update model structure docs to explain what happens when LITTER_POOL is off (#135) * updated documentation to describe what happens when LITTER_POOL=0 * remove redundant explanation --------- Co-authored-by: David LeBauer <[email protected]>
1 parent e6ca799 commit e7017ca

File tree

13 files changed

+197
-46
lines changed

13 files changed

+197
-46
lines changed

docs/model-structure.md

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,17 @@ component out of
5454
-->
5555
## Carbon Dynamics
5656

57+
58+
### Litter Pool
59+
60+
SIPNET can be run with or without a separate litter pool (LITTER_POOL=1 or 0).
61+
Equations in this document assume LITTER_POOL=1 unless otherwise noted.
62+
63+
When LITTER_POOL=0:
64+
- Carbon fluxes that would go to the litter pool ($F^C_\text{litter}$) are routed directly to the soil carbon pool
65+
- All decomposition occurs in the soil pool
66+
- This affects carbon routing from harvest events, organic matter additions, plant senescence, and other processes involving $F^C_\text{litter}$
67+
5768
### Maximum Photosynthetic Rate
5869

5970
$$
@@ -168,7 +179,8 @@ $$
168179
R_\text{wood} = K_\text{wood} \cdot C_\text{wood} \cdot D_{\text{temp,Q10}_v} \tag{Braswell A19}\label{eq:A19}
169180
$$
170181

171-
Wood maintenance respiration $(R_m)$ depends on the wood carbon content $(C_\text{wood})$, a scaling constant $(k_\text{wood})$, and the temperature sensitivity scaling function $D_{\text{temp,Q10}_v}$.
182+
Wood maintenance respiration $(R_m)$ depends on the wood carbon content $(C_\text{wood})$,
183+
a scaling constant $(k_\text{wood})$, and the temperature sensitivity scaling function $D_{\text{temp,Q10}_v}$.
172184

173185

174186
### Litter Carbon
@@ -221,7 +233,7 @@ The rate of decomposition is a function of the litter carbon content and the dec
221233
### Soil Carbon
222234

223235
$$
224-
\frac{dC_\text{soil}}{dt} = F^C_\text{soil} - R_{H_\text{soil}} \tag{Braswell A3}\label{eq:A3}
236+
\frac{dC_\text{soil}}{dt} = F^C_{\text{soil}} - R_{H_\text{soil}} \tag{Braswell A3}\label{eq:A3}
225237
$$
226238

227239
The change in the SOC pool over time $\frac{dC_\text{soil}}{dt}$ is determined by the addition of litter carbon and the loss of carbon to heterotrophic respiration. This model assumes no loss of SOC to leaching or erosion.
@@ -579,25 +591,30 @@ For the relationship between $N_2O$ flux and soil moisture, Wang et al (2023) su
579591

580592
## $\frak{Agronomic \ Management \ Events}$
581593

582-
All management events are specified in the `events.in`. Each event is a separate record that includes the date of the event, the type of event, and associated parameters.
594+
All management events are specified in the `events.in`. Each event is a separate record that includes the
595+
date of the event, the type of event, and associated parameters.
583596

584-
### $\frak{Fertilizer \ and \ Organic \ Matter \ Additions}$
597+
### $\frak{Fertilizer}$ and Organic Matter Additions
585598

586-
Additions of Mineral N, Organic N, and Organic C are represented by the fluxes $F^N_{\text{fert,min}}$, $F^N_{\text{fert,org}},$ and $F^C_{\text{fert,org}}$ that are specified in the `events.in` configuration file.
599+
Additions of Mineral N, Organic N, and Organic C are added directly to their respective pools via the
600+
fluxes $F^N_{\text{fert,min}}$, $F^N_{\text{fert,org}},$ and $F^C_{\text{fert,org}}$ that are specified
601+
in the `events.in` configuration file.
587602

588603
Event parameters specified in the `events.in` file:
589-
590604
- Organic N added $(F^N_{\text{fert,org}})$
591605
- Organic C added $(F^C_{\text{fert,org}})$
592606
- Mineral N added $(F^N_{\text{fert,min}})$
593607

594-
These are added to the litter C and N and mineral N pools, respectively.
595-
596-
Mineral N includes fertilizer supplied as NO3, NH4, and Urea-N. Urea-N is assumed to hydrolyze to ammonium and bicarbonate rapidly and is treated as a mineral N pool. This is a common assumption because of the high rate of this conversion, and is consistent the DayCent formulation (Parton et al TK-ref, other models and refs?). Only relatively recently did DayCent explicitly model Urea-N to NH4 in order to represent the impact of urease inhibitors (Gurung et al 2021) that slow down the rate.
608+
Mineral N includes fertilizer supplied as NO3, NH4, and Urea-N. Urea-N is assumed to hydrolyze to ammonium
609+
and bicarbonate rapidly and is treated as a mineral N pool. This is a common model assumption because of
610+
the fast conversion of Urea to ammonium, and is consistent with the DayCent formulation (Parton et al, 2001).
611+
Only relatively recently did DayCent explicitly model Urea-N to NH4 in order to represent the impact of
612+
urease inhibitors (Gurung et al 2021) that slow down the rate.
597613

598614
### $\frak{Tillage}$
599615

600-
To represent tillage, we define two new adjustment factors that modify the decomposition rates of litter $K_{\text{litter}}$ and soil organic matter $K_{\text{som}}$:
616+
To represent tillage, we define two new adjustment factors that modify the decomposition rates
617+
of litter $K_{\text{litter}}$ and soil organic matter $K_{\text{som}}$:
601618

602619
Event parameters from the `events.in` file:
603620

src/common/context.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ void updateCharContext(const char *name, const char *value,
137137
int hasSourcePrecedence(struct context_metadata *s,
138138
context_source_t newSource) {
139139
// If newSource is greater (or equal) to old source, then it's good
140-
return (s->source < newSource);
140+
return (s->source <= newSource);
141141
}
142142

143143
// Get a printable version of source enum for dumpConfig()

src/sipnet/sipnet.c

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1742,6 +1742,7 @@ void processEvents(void) {
17421742
const double fracTA = harvParams->fractionTransferredAbove;
17431743
const double fracRB = harvParams->fractionRemovedBelow;
17441744
const double fracTB = harvParams->fractionTransferredBelow;
1745+
char carbonPool[20] = "";
17451746

17461747
// Litter increase
17471748
const double litterAdd = fracTA * (envi.plantLeafC + envi.plantWoodC) +
@@ -1755,15 +1756,22 @@ void processEvents(void) {
17551756
const double coarseDelta = -envi.coarseRootC * (fracRB + fracTB);
17561757

17571758
// Pool updates:
1758-
envi.litter += litterAdd;
1759+
if (ctx.litterPool) {
1760+
envi.litter += litterAdd;
1761+
strcpy(carbonPool, "env.litter");
1762+
} else {
1763+
// If the litter pool is not turned on, add it to the soil pool
1764+
envi.soil += litterAdd;
1765+
strcpy(carbonPool, "env.soil");
1766+
}
17591767
envi.plantLeafC += leafDelta;
17601768
envi.plantWoodC += woodDelta;
17611769
envi.fineRootC += fineDelta;
17621770
envi.coarseRootC += coarseDelta;
17631771

17641772
// FUTURE: move/remove biomass in N pools
17651773

1766-
writeEventOut(eventOutFile, event, 5, "env.litter", litterAdd,
1774+
writeEventOut(eventOutFile, event, 5, carbonPool, litterAdd,
17671775
"envi.plantLeafC", leafDelta, "envi.plantWoodC",
17681776
woodDelta, "envi.fineRootC", fineDelta,
17691777
"envi.coarseRootC", coarseDelta);
@@ -1772,10 +1780,32 @@ void processEvents(void) {
17721780
// TBD
17731781
printf("Tillage events not yet implemented\n");
17741782
break;
1775-
case FERTILIZATION:
1776-
// TBD
1777-
printf("Fertilization events not yet implemented\n");
1778-
break;
1783+
case FERTILIZATION: {
1784+
const FertilizationParams *fertParams = event->eventParams;
1785+
// const double orgN = fertParams->orgN;
1786+
const double orgC = fertParams->orgC;
1787+
// const double minN = fertParams->minN;
1788+
char carbonPool[20] = "";
1789+
1790+
// Pool updates:
1791+
if (ctx.litterPool) {
1792+
envi.litter += orgC;
1793+
strcpy(carbonPool, "env.litter");
1794+
// orgN
1795+
// minN
1796+
} else {
1797+
// If the litter pool is not turned on, add it to the soil pool
1798+
envi.soil += orgC;
1799+
strcpy(carbonPool, "env.soil");
1800+
// orgN
1801+
// minN
1802+
}
1803+
1804+
// FUTURE: allocate to N pools
1805+
1806+
// This will (likely) be 3 params eventually
1807+
writeEventOut(eventOutFile, event, 1, carbonPool, orgC);
1808+
} break;
17791809
default:
17801810
printf("Unknown event type (%d) in processEvents()\n", event->type);
17811811
exit(EXIT_CODE_UNKNOWN_EVENT_TYPE_OR_PARAM);

tests/sipnet/test_bugfixes/testEventFileOrderChecks.c

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ int run(void) {
2828
test_assert(jmp_rval == 0);
2929
status |= !exit_result;
3030
if (!exit_result) {
31-
printf("FAIL with infra_events_year_boundary.in\n");
31+
logTest("FAIL with infra_events_year_boundary.in\n");
3232
}
3333

3434
// Second test - this should exit
@@ -42,7 +42,7 @@ int run(void) {
4242
test_assert(jmp_rval == 1);
4343
status |= !exit_result;
4444
if (!exit_result) {
45-
printf("FAIL with infra_events_bad_order.in\n");
45+
logTest("FAIL with infra_events_bad_order.in\n");
4646
}
4747

4848
// Allow a real exit, not that this is really needed
@@ -54,13 +54,13 @@ int run(void) {
5454
int main(void) {
5555
int status;
5656

57-
printf("Starting testEventFileOrderChecks:run()\n");
57+
logTest("Starting testEventFileOrderChecks:run()\n");
5858
status = run();
5959
if (status) {
6060
really_exit = 1;
61-
printf("FAILED testEventFileOrderChecks with status %d\n", status);
61+
logTest("FAILED testEventFileOrderChecks with status %d\n", status);
6262
exit(status);
6363
}
6464

65-
printf("PASSED testEventFileOrderChecks\n");
65+
logTest("PASSED testEventFileOrderChecks\n");
6666
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
2023 65 plant envi.plantLeafC=10.00,envi.plantWoodC=5.00,envi.fineRootC=4.00,envi.coarseRootC=3.00
22
2023 70 irrig envi.soilWater=5.00,fluxes.immedEvap=0.00
3-
2023 200 harv env.litter=12.40,envi.plantLeafC=-4.80,envi.plantWoodC=-3.20,envi.fineRootC=-4.80,envi.coarseRootC=-4.80
3+
2023 200 harv env.soil=12.40,envi.plantLeafC=-4.80,envi.plantWoodC=-3.20,envi.fineRootC=-4.80,envi.coarseRootC=-4.80
44
2024 65 plant envi.plantLeafC=10.00,envi.plantWoodC=5.00,envi.fineRootC=4.00,envi.coarseRootC=3.00
55
2024 70 irrig envi.soilWater=2.50,fluxes.immedEvap=2.50
6-
2024 200 harv env.litter=12.14,envi.plantLeafC=-10.32,envi.plantWoodC=-5.88,envi.fineRootC=-2.88,envi.coarseRootC=-2.48
6+
2024 200 harv env.soil=12.14,envi.plantLeafC=-10.32,envi.plantWoodC=-5.88,envi.fineRootC=-2.88,envi.coarseRootC=-2.48

tests/sipnet/test_events_infrastructure/testEventOutputFile.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,14 @@ int run(void) {
8484
initContext();
8585
updateIntContext("events", 1, CTX_TEST);
8686

87+
updateIntContext("litterPool", 0, CTX_TEST); // default, but to be sure
8788
status = runTest("events_output_no_header", 0);
8889
if (status) {
8990
logTest("runTest(no_header) failed\n");
9091
}
9192
testStatus |= status;
9293

94+
updateIntContext("litterPool", 1, CTX_TEST);
9395
status = runTest("events_output_header", 1);
9496
if (status) {
9597
logTest("runTest(header) failed\n");

tests/sipnet/test_events_types/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ LDFLAGS=-L$(ROOT_DIR)/libs
88
LDLIBS=-lsipnet -lsipnet_common -lm
99

1010
# List test files in this directory here
11-
TEST_CFILES=testEventIrrigation.c testEventPlanting.c testEventHarvest.c
11+
TEST_CFILES=testEventIrrigation.c testEventPlanting.c testEventHarvest.c testEventFertilization.c
1212

1313
# The rest is boilerplate, likely copyable as is to a new test directory
1414
TEST_OBJ_FILES=$(TEST_CFILES:%.c=%.o)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
2024 70 fert 15 5 10 # fertilized with 15 g/m2 organic N, 5 g/m2 organic C, and 10 g/m2 mineral N on day 40
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2024 70 fert 15 5 10 # fertilized with 15 g/m2 organic N, 5 g/m2 organic C, and 10 g/m2 mineral N on day 40
2+
2024 70 fert 5 2 3 # fertilized with 15 g/m2 organic N, 5 g/m2 organic C, and 10 g/m2 mineral N on day 40
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
#include <stdio.h>
2+
#include <stdlib.h>
3+
4+
#include "utils/tUtils.h"
5+
6+
#include "typesUtils.h"
7+
8+
int checkOutput(double litterC) {
9+
int status = 0;
10+
double curLitterC = 0;
11+
if (ctx.litterPool) {
12+
logTest("Checking litter pool\n");
13+
curLitterC = envi.litter;
14+
} else {
15+
logTest("Checking soil pool\n");
16+
curLitterC = envi.soil;
17+
litterC += 0.5; // We bumped init soil C to distinguish
18+
}
19+
if (!compareDoubles(litterC, curLitterC)) {
20+
logTest("Litter/soil C is %f, expected %f\n", curLitterC, litterC);
21+
status = 1;
22+
}
23+
return status;
24+
}
25+
26+
void initEnv(void) {
27+
envi.soil = 1.5;
28+
envi.litter = 1;
29+
// Others to be added for N
30+
}
31+
32+
int run(void) {
33+
int status = 0;
34+
double expLitterC;
35+
36+
// We will need to switch back and forth between litter pool and soil manually
37+
prepTypesTest();
38+
39+
// init values
40+
initEnv();
41+
42+
//// ONE PLANTING EVENT
43+
updateIntContext("litterPool", 0, CTX_TEST);
44+
logTest("Litter pool is %s\n", ctx.litterPool ? "on" : "off");
45+
initEvents("events_one_fert.in", 0);
46+
setupEvents();
47+
processEvents();
48+
49+
// First fert: (15-5-10)
50+
expLitterC = 1 + 5;
51+
// litterN + 15
52+
// minN + 10
53+
status |= checkOutput(expLitterC);
54+
55+
//// TWO HARVEST EVENTS
56+
updateIntContext("litterPool", 1, CTX_TEST);
57+
logTest("Litter pool is %s\n", ctx.litterPool ? "on" : "off");
58+
initEnv();
59+
initEvents("events_two_fert.in", 1);
60+
setupEvents();
61+
processEvents();
62+
// First event same as above (15-5-10)
63+
expLitterC = 1 + 5;
64+
// litterN
65+
// minN
66+
// Second fert (5-2-3)
67+
expLitterC += 2;
68+
// litterN += 5
69+
// minN += 3
70+
status |= checkOutput(expLitterC);
71+
72+
return status;
73+
}
74+
75+
int main(void) {
76+
logTest("Starting run()\n");
77+
int status = run();
78+
if (status) {
79+
logTest("FAILED testEventFertilization with status %d\n", status);
80+
exit(status);
81+
}
82+
83+
logTest("PASSED testEventFertilization\n");
84+
}

0 commit comments

Comments
 (0)