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
27 changes: 18 additions & 9 deletions src/sipnet/limitations.c
Original file line number Diff line number Diff line change
Expand Up @@ -151,22 +151,31 @@ static void checkNegativeCreation(void) {

double len = climate->length;
// Above ground
// If leafCreation is too negative, we need to deduct from wood instead
// Use only the continuous turnover term to match previous logic - but see
// SIPNET issue #372.
double leafLitterTurnover = envi.plantLeafC * params.leafTurnoverRate;
double leafDeficit =
envi.plantLeafC / len + fluxes.leafCreation - leafLitterTurnover;
// If leafCreation is too negative, we need to deduct from wood instead.
// Account for continuous turnover, phenology leaf-off, event leaf-off,
// leaf-on allocations, and event leaf C additions.
double leafDeficit = envi.plantLeafC / len + fluxes.leafCreation +
fluxes.leafOnCreation + fluxes.eventLeafC +
fluxes.eventLeafOnCreation - fluxes.leafLitter -
fluxes.eventLeafOffLitter;
if (leafDeficit < 0) {
fluxes.woodCreation += leafDeficit;
fluxes.leafCreation -= leafDeficit;
}

// Below ground
double fineRootDeficit =
envi.fineRootC / len + fluxes.fineRootCreation - fluxes.fineRootLoss;
// Account for event fluxes and leaf-on allocations from coarse root
double leafOnFromRoot =
fluxes.leafOnCreation - fluxes.leafOnCreationFromWood;
double eventLeafOnFromRoot =
fluxes.eventLeafOnCreation - fluxes.eventLeafOnCreationFromWood;

double fineRootDeficit = envi.fineRootC / len + fluxes.fineRootCreation +
fluxes.eventFineRootC - fluxes.fineRootLoss;
double coarseRootDeficit = envi.coarseRootC / len +
fluxes.coarseRootCreation - fluxes.coarseRootLoss;
fluxes.coarseRootCreation +
fluxes.eventCoarseRootC - fluxes.coarseRootLoss -
leafOnFromRoot - eventLeafOnFromRoot;
if ((fineRootDeficit < 0.0) != (coarseRootDeficit < 0.0)) {
// If neither are negative, nothing to do
// If both are negative, the plant will die in checkForMortality()
Expand Down
78 changes: 78 additions & 0 deletions tests/sipnet/test_modeling/testFluxCalculations.c
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,83 @@ int testRootFluxesNegativeNPP(void) {
return status;
}

int testLeafOffAndEventDeficits(void) {
int status = 0;
logTest("Running testLeafOffAndEventDeficits\n");

// Test 1: Phenology leaf-off litter causes leaf deficit
resetContext();
resetFluxVars();
envi.plantLeafC = 1.0;
envi.plantWoodC = 10.0;
climate->length = 0.125;

fluxes.leafCreation = -10.0;
fluxes.woodCreation = 0.0;
fluxes.leafLitter = 16.0; // turnover + phenology leaf-off

// Initial total creation = -10.0
// leafDeficit = 1.0/0.125 + (-10.0) - 16.0 = 8.0 - 10.0 - 16.0 = -18.0 < 0
// woodCreation += -18.0 => -18.0
// leafCreation -= -18.0 => +8.0
checkCarbonLimitations();

status |= checkFlux(fluxes.leafCreation, 8.0, "leafCreation (phenology leaf-off deficit)");
status |= checkFlux(fluxes.woodCreation, -18.0, "woodCreation (phenology leaf-off deficit)");
// Verify carbon conservation (sum of creation fluxes unchanged)
status |= checkFlux(fluxes.leafCreation + fluxes.woodCreation, -10.0,
"above-ground carbon conservation (phenology leaf-off)");

// Test 2: Event leaf-off litter causes leaf deficit
resetContext();
resetFluxVars();
envi.plantLeafC = 2.0;
envi.plantWoodC = 10.0;
climate->length = 0.125;

fluxes.leafCreation = -10.0;
fluxes.woodCreation = 0.0;
fluxes.eventLeafOffLitter = 20.0;

// leafDeficit = 2.0/0.125 + (-10.0) - 20.0 = 16.0 - 10.0 - 20.0 = -14.0 < 0
// woodCreation += -14.0 => -14.0
// leafCreation -= -14.0 => 4.0
checkCarbonLimitations();

status |= checkFlux(fluxes.leafCreation, 4.0, "leafCreation (event leaf-off deficit)");
status |= checkFlux(fluxes.woodCreation, -14.0, "woodCreation (event leaf-off deficit)");
status |= checkFlux(fluxes.leafCreation + fluxes.woodCreation, -10.0,
"above-ground carbon conservation (event leaf-off)");

// Test 3: Below-ground deficit with event root fluxes and leaf-on root allocation
resetContext();
resetFluxVars();
envi.fineRootC = 1.0;
envi.coarseRootC = 10.0;
climate->length = 0.125;

fluxes.fineRootCreation = -10.0;
fluxes.coarseRootCreation = 5.0;
fluxes.fineRootLoss = 0.0;
fluxes.coarseRootLoss = 0.0;
fluxes.leafOnCreation = 4.0;
fluxes.leafOnCreationFromWood = 1.0;
// leafOnFromRoot = 4.0 - 1.0 = 3.0

// fineRootDeficit = 1.0/0.125 + (-10.0) - 0.0 = -2.0 < 0
// coarseRootDeficit = 10.0/0.125 + 5.0 - 0.0 - 3.0 = 82.0 > 0
// coarseRootCreation += -2.0 => 3.0
// fineRootCreation -= -2.0 => -8.0
checkCarbonLimitations();

status |= checkFlux(fluxes.fineRootCreation, -8.0, "fineRootCreation (root event/leaf-on deficit)");
status |= checkFlux(fluxes.coarseRootCreation, 3.0, "coarseRootCreation (root event/leaf-on deficit)");
status |= checkFlux(fluxes.fineRootCreation + fluxes.coarseRootCreation, -5.0,
"below-ground carbon conservation");

return status;
}

int run(void) {
int status = 0;

Expand All @@ -330,6 +407,7 @@ int run(void) {
status |= testWoodAndLeafFluxesWithAccountingDelta();
status |= testRootFluxesPositiveNPP();
status |= testRootFluxesNegativeNPP();
status |= testLeafOffAndEventDeficits();

return status;
}
Expand Down
2 changes: 1 addition & 1 deletion tests/smoke/russell_1/sipnet.config
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Final config for SIPNET run at 2026-08-29 02:02:30 UTC
Final config for SIPNET run at 2026-09-10 08:35:57 UTC
Name Source Value
ANAEROBIC DEFAULT 0
CARBON_SATURATION DEFAULT 0
Expand Down
Loading