From 917b4a990d1baf87ad5d06f8bc3b39810a62f6c5 Mon Sep 17 00:00:00 2001 From: Mike Longfritz Date: Wed, 13 May 2026 12:14:38 -0400 Subject: [PATCH 1/2] Interim commit --- src/sipnet/limitations.c | 67 +++++++++++++++++++++++++++++++++++++++- src/sipnet/nitrogen.c | 23 ++++++++++---- src/sipnet/nitrogen.h | 4 +-- 3 files changed, 85 insertions(+), 9 deletions(-) diff --git a/src/sipnet/limitations.c b/src/sipnet/limitations.c index 918c109d..bcc55c24 100644 --- a/src/sipnet/limitations.c +++ b/src/sipnet/limitations.c @@ -9,6 +9,68 @@ #include "nitrogen.h" #include "state.h" +/** + * Check for carbon and nitrogen limitations for leaf-on events + */ +static void checkLeafOnLimitation(void) { + // Leaf on events are limited by: + // * leafGrowth parameter (this is already calculated) + // * available carbon + // * available nitrogen + double leafOnFlux = fluxes.leafOnCreation + fluxes.eventLeafOnCreation; + double leafOnCDemand = leafOnFlux * climate->length; + double leafOnNDemand = 0.0; + double nLimitation = 1.0; + double availableN = 0.0; + + if (leafOnCDemand < TINY) { + // Nothing to check + return; + } + + // First up, carbon. We do not draw from the storage pool for this. + double availableC = envi.plantWoodC + envi.coarseRootC; + double cLimitation = (availableC > TINY) ? (availableC / leafOnCDemand) : 0; + + // Next, nitrogen. This one is a little trickier, as 'available nitrogen' is + // harder to calculate (but we defer that to the nitrogen module). + if (ctx.nitrogenCycle) { + // Needed N for this transfer is (what leaves need) - (what wood provides) + // Reminder: both wood and coarseRoot use params.woodCN + leafOnNDemand = + leafOnCDemand / params.leafCN - leafOnCDemand / params.woodCN; + availableN = calcAvailableNitrogen(); + nLimitation = (availableN > TINY) ? (availableN / leafOnNDemand) : 0; + } + double limitation = fmin(cLimitation, nLimitation); + limitation = fmax(fmin(limitation, 1.0), 0.0); + + if (limitation < 1) { + if (ctx.nitrogenCycle) { + logInfo("Leaf on creation %.4f C / %.4f N exceeds available C %.4f and N " + "%.4f, " + "reducing leaf on by %.2f%% on year %d day %d time %.3f\n", + leafOnCDemand, leafOnNDemand, availableC, availableN, + (1 - limitation) * 100, climate->year, climate->day, + climate->time); + logInfo("envi.minN %.3f fluxes.nMin %.3f fluxes.nVol %.3f fluxes.nLeach " + "%.3f fluxes.nFix %.3f fluxes.nUptake %.3f\n", + envi.minN, fluxes.nMin * climate->length, + fluxes.nVolatilization * climate->length, + fluxes.nLeaching * climate->length, + fluxes.nFixation * climate->length, + fluxes.nUptake * climate->length); + } else { + logInfo("Leaf on creation %.4f exceeds available C %.4f, " + "reducing leaf on by %.2f%% on year %d day %d time %.3f\n", + leafOnCDemand, availableC, (1 - limitation) * 100, climate->year, + climate->day, climate->time); + } + fluxes.leafOnCreation *= limitation; + fluxes.eventLeafOnCreation *= limitation; + } +} + /** * Check for nitrogen limitation, and reduce growth if needed */ @@ -46,7 +108,10 @@ static void checkNitrogenLimitation(void) { // See limitations.h void checkLimitations(void) { - // EXPECTED: calcLeafOnLimitation() + + // First up - leaf on. This should be before the nitrogen limitation check, + // as the two are not independent. + checkLeafOnLimitation(); if (ctx.nitrogenCycle) { checkNitrogenLimitation(); diff --git a/src/sipnet/nitrogen.c b/src/sipnet/nitrogen.c index 6a4f2a96..b511011e 100644 --- a/src/sipnet/nitrogen.c +++ b/src/sipnet/nitrogen.c @@ -84,6 +84,23 @@ static void calcNPoolFluxes(void) { fluxes.nMin = litterMin + soilMin; } +/** + * Sum all mineral N fluxes for this time step + */ +static double calcMinNFluxes(void) { + return calcMinNNonUptakeFluxes() - fluxes.nUptake; +} + +// see nitrogen.h +double calcAvailableNitrogen(void) { + // Return "available nitrogen", which is mineral N +/- relevant fluxes + // from this time step. + double availableN = envi.minN; + double minNDelta = calcMinNFluxes() * climate->length; + + return availableN + minNDelta; +} + // see nitrogen.h double calcPlantNDemand(void) { if (!ctx.nitrogenCycle) { @@ -130,12 +147,6 @@ double calcNFixationFrac(void) { return params.nFixationFracMax * nFixationInhibition; } -// see nitrogen.h -double calcAvailableNitrogen(void) { - // NOT USED YET - return 0.0; -} - // see nitrogen.h void calcNitrogenFluxes(void) { calcNVolatilizationFlux(); diff --git a/src/sipnet/nitrogen.h b/src/sipnet/nitrogen.h index 4d549074..b8d07f86 100644 --- a/src/sipnet/nitrogen.h +++ b/src/sipnet/nitrogen.h @@ -3,13 +3,13 @@ // Nitrogen cycle related functions -// TBD: will be used for leaf-on limit check; might be folded into -// nitrogen use too, if appropriate /*! * Calculate available nitrogen for this step * * Takes into account demand, fixation, and... * + * This function should only be called AFTER fluxes are calculated + * * @return available nitrogen */ double calcAvailableNitrogen(void); From fcafda203c68ca4492a8c6abc8e22014d3b57828 Mon Sep 17 00:00:00 2001 From: Mike Longfritz Date: Wed, 13 May 2026 15:32:55 -0400 Subject: [PATCH 2/2] Pre-N-storage commit; things will change with that pool --- src/sipnet/limitations.c | 13 +++++++++---- src/sipnet/nitrogen.c | 31 +++++++++++-------------------- src/sipnet/nitrogen.h | 12 +++++++++++- 3 files changed, 31 insertions(+), 25 deletions(-) diff --git a/src/sipnet/limitations.c b/src/sipnet/limitations.c index bcc55c24..2971f755 100644 --- a/src/sipnet/limitations.c +++ b/src/sipnet/limitations.c @@ -39,15 +39,22 @@ static void checkLeafOnLimitation(void) { // Reminder: both wood and coarseRoot use params.woodCN leafOnNDemand = leafOnCDemand / params.leafCN - leafOnCDemand / params.woodCN; - availableN = calcAvailableNitrogen(); + double minNFluxes = calcMinNNonUptakeFluxes() + fluxes.eventMinN; + availableN = envi.minN + minNFluxes * climate->length; nLimitation = (availableN > TINY) ? (availableN / leafOnNDemand) : 0; } double limitation = fmin(cLimitation, nLimitation); limitation = fmax(fmin(limitation, 1.0), 0.0); if (limitation < 1) { + fluxes.leafOnCreation *= limitation; + fluxes.eventLeafOnCreation *= limitation; if (ctx.nitrogenCycle) { - logInfo("Leaf on creation %.4f C / %.4f N exceeds available C %.4f and N " + // We need to recalculate fixation and uptake since the demand has now + // changed + calcNFixationAndUptakeFluxes(); + + logInfo("Leaf on creation %.4f C / %.4f N exceeds available C %.4f / N " "%.4f, " "reducing leaf on by %.2f%% on year %d day %d time %.3f\n", leafOnCDemand, leafOnNDemand, availableC, availableN, @@ -66,8 +73,6 @@ static void checkLeafOnLimitation(void) { leafOnCDemand, availableC, (1 - limitation) * 100, climate->year, climate->day, climate->time); } - fluxes.leafOnCreation *= limitation; - fluxes.eventLeafOnCreation *= limitation; } } diff --git a/src/sipnet/nitrogen.c b/src/sipnet/nitrogen.c index b511011e..b50d3985 100644 --- a/src/sipnet/nitrogen.c +++ b/src/sipnet/nitrogen.c @@ -39,18 +39,6 @@ static void calcNLeachingFlux(void) { fluxes.nLeaching = envi.minN * phi * params.nLeachingFrac; } -/*! - * Calculate plant N fixation and uptake fluxes - */ -static void calcNFixationAndUptakeFluxes(void) { - // These values may change later if we are under nitrogen limitation - double nDemandFlux = calcPlantNDemand(); - double nFixationFrac = calcNFixationFrac(); - - fluxes.nFixation = nFixationFrac * nDemandFlux; - fluxes.nUptake = (1 - nFixationFrac) * nDemandFlux; -} - /** * Calculate nitrogen fluxes for soil and litter pools */ @@ -84,19 +72,12 @@ static void calcNPoolFluxes(void) { fluxes.nMin = litterMin + soilMin; } -/** - * Sum all mineral N fluxes for this time step - */ -static double calcMinNFluxes(void) { - return calcMinNNonUptakeFluxes() - fluxes.nUptake; -} - // see nitrogen.h double calcAvailableNitrogen(void) { // Return "available nitrogen", which is mineral N +/- relevant fluxes // from this time step. double availableN = envi.minN; - double minNDelta = calcMinNFluxes() * climate->length; + double minNDelta = calcMinNNonUptakeFluxes() * climate->length; return availableN + minNDelta; } @@ -147,6 +128,16 @@ double calcNFixationFrac(void) { return params.nFixationFracMax * nFixationInhibition; } +// See nitrogen.h +void calcNFixationAndUptakeFluxes(void) { + // These values may change later if we are under nitrogen limitation + double nDemandFlux = calcPlantNDemand(); + double nFixationFrac = calcNFixationFrac(); + + fluxes.nFixation = nFixationFrac * nDemandFlux; + fluxes.nUptake = (1 - nFixationFrac) * nDemandFlux; +} + // see nitrogen.h void calcNitrogenFluxes(void) { calcNVolatilizationFlux(); diff --git a/src/sipnet/nitrogen.h b/src/sipnet/nitrogen.h index b8d07f86..aff91a6f 100644 --- a/src/sipnet/nitrogen.h +++ b/src/sipnet/nitrogen.h @@ -6,7 +6,10 @@ /*! * Calculate available nitrogen for this step * - * Takes into account demand, fixation, and... + * Calculate available nitrogen as mineral N pool plus relevant fluxes: + * - mineralization + * - leaching + * - volatilization * * This function should only be called AFTER fluxes are calculated * @@ -37,6 +40,13 @@ double calcMinNNonUptakeFluxes(void); */ double calcNFixationFrac(void); +/*! + * Calculate plant N fixation and uptake fluxes. + * + * This function is called directly by the leaf-on limitation check + */ +void calcNFixationAndUptakeFluxes(void); + /*! * Calculate all nitrogen fluxes *