# -*- coding: utf-8 -*-
# Copyright (c) 2004-2024 Wageningen Environmental Research, Wageningen-UR
# Herman Berghuijs (herman.berghuijs@wur.nl) and Allard de Wit (allard.dewit@wur.nl), January 2024
import numpy as np
from .. import exceptions as exc
from pcse.decorators import prepare_rates, prepare_states
from pcse.base import ParamTemplate, StatesTemplate, RatesTemplate, \
SimulationObject
from pcse import signals
from ..traitlets import Float, Int, Instance, Bool
# from .soiln_profile import SoilNProfile
[docs]class SNOMIN(SimulationObject):
"""
SNOMIN (Soil Nitrogen module for Mineral and Inorganic Nitrogen) is a layered soil nitrogen balance. A
full mathematical description of the model is given by Berghuijs et al (2024).
Berghuijs HNC, Silva JV, Reidsma P, De Wit AJW (2024) Expanding the WOFOST crop model
to explore options for sustainable nitrogen management: A study for winter wheat in
the Netherlands. European Journal of Agronomy 154 ARTN 127099. https://doi.org/10.1016/j.eja.2024.127099
**Simulation parameters:**
========== ==================================================== ====================
Name Description Unit
========== ==================================================== ====================
A0SOM Initial age of soil organic material y
CNRatioBio C:N ratio of microbial biomass kg C kg-1 N
FASDIS Fraction of assimilation to dissimilation -
KDENIT_REF Reference first order denitrification rate constant d-1
KNIT_REF Reference first order nitrification rate constant d-1
KSORP Sorption coefficient ammonium (m3 water kg-1 soil) m3 soil kg-1 soil
MRCDIS Michaelis Menten constant for response factor
denitrification to soil respiration kg C m-2 d-1
NO3ConcR NO3-N concentration in rain water mg NO3--N L- water
NH4ConcR NH4-N concentration in rain water mg NH4+-N L-1 water
NO3I Initial amount of NO3-N :sup:`1` kg NO3--N ha-1
NH4I Initial amount of NH4-N :sup:`1` kg NH4+-N ha-1)
WFPS_CRIT Critical water filled pore space fraction m3 water m-3 pore
========== ==================================================== ====================
:sup:`1` This state variable is defined for each soil layer
**State variables**
========== ==================================================== ==============
Name Description Unit
========== ==================================================== ==============
AGE Appearant age of amendment (d) :sup:`1` d
ORGMAT Amount of organic matter (kg ORG ha-1) :sup:`1` kg OM m-2
CORG Amount of C in organic matter (kg C ha-1) :sup:`1` kg C m-2
NORG Amount of N in organic matter (kg N ha-1) :sup:`1` kg N m-2
NH4 Amount of NH4-N (kg N ha-1) :sup:`2` kg NH4-N m-2
NO3 Amount of NO3-N (kg N ha-1) :sup:`2` kg NO3-N m-2
========== ==================================================== ==============
| :sup:`1` This state variable is defined for each combination of soil layer and amendment
| :sup:`2` This state variable is defined for each soil layer
**Rate variables**
========== ================================================== ====================
Name Description Unit
========== ================================================== ====================
RAGE Rate of change of apparent age :sup:`2` d d-1
RAGEAM Initial apparent age :sup:`2` d d-1
RAGEAG Rate of ageing of amendment :sup:`2` d d-1
RCORG Rate of change of organic C :sup:`2` kg C m-2 d-1
RCORGAM Rate pf application organic C :sup:`2` kg C m-2 d-1
RCORGDIS Dissimilation rate of organic C :sup:`2` kg C m-2 d-1
RNH4 Rate of change amount of NH4+-N :sup:`1` kg NH4+-N m-2 d-1
RNH4AM Rate of NH4+-N application :sup:`1` kg NH4+-N m-2 d-1
RNH4DEPOS Rate of NH4-N deposition :sup:`1` kg NH4+-N m-2 d-1
RNH4IN Rate of NH4+-N inflow from adjacent layer :sup:`1` kg NH4+-N m-2 d-1
RNH4MIN Net rate of mineralization :sup:`1` kg NH4+-N m-2 d-1
RNH4NITR Rate of nitrification :sup:`1` kg NH4+-N m-2 d-1
RNH4OUT Rate of NH4+-N outflow to adjacent layer :sup:`1` kg NH4+-N m-2 d-1
RNH4UP Rate of NH4+-N root uptake :sup:`1` kg NH4+-N m-2 d-1
RNO3 Rate of change amount of NO3--N :sup:`1` kg NO3--N m-2 d-1
RNO3AM Rate of NO3--N application :sup:`1` kg NO3--N m-2 d-1
RNO3DENITR Rate of denitrification :sup:`1` kg NO3--N m-2 d-1
RNO3DEPOS Rate of NO3--N deposition :sup:`1` kg NO3--N m-2 d-1
RNO3IN Rate of NH4+-N inflow from adjacent layer :sup:`1` kg NO3+-N m-2 d-1
RNO3NITR Rate of nitrification :sup:`1` kg NO3--N m-2 d-1
RNO3OUT Rate of NO3--N outflow to adjacent layer :sup:`1` kg NO3--N m-2 d-1
RNO3UP Rate of NO3--N root uptake :sup:`1` kg NO3--N m-2 d-1
RNORG Rate of change of organic N :sup:`2` kg N m-2 d-1
RNORGAM Rate pf application organic N :sup:`2` kg N m-2 d-1
RNORGDIS Dissimilation rate of organic matter :sup:`2` kg N m-2 d-1
RORGMAT Rate of change of organic material :sup:`2` kg OM m-2 d-1
RORGMATAM Rate of application organic matter :sup:`2` kg OM m-2 d-1
RORGMATDIS Dissimilation rate of organic matter :sup:`2` kg OM m-2 d-1
========== ================================================== ====================
| :sup:`1` This state variable is defined for each soil layer
| :sup:`2` This state variable is defined for each combination of soil layer and amendment
**Signals send or handled**
`SNOMIN` receives the following signals:
* APPLY_N_SNOMIN: Is received when an external input from N fertilizer
is provided. See `_on_APPLY_N_SNOMIN()` and `signals.apply_n_snomin` for details.
"""
# Placeholders initial values
_ORGMATI = None
_CORGI = None
_NORGI = None
_NH4I = None
_NO3I = None
# Placeholders
_RNO3AM = None
_RNH4AM = None
_RAGEAM = None
_RORGMATAM = None
_RCORGAM = None
_RNORGAM = None
# Unit conversions
g_to_kg = 1e-3
cm_to_m = 1e-2
cm2_to_ha = 1e-8
cm3_to_m3 = 1e-6
ha_to_m2 = 1e-4
m2_to_ha = 1e-4
y_to_d = 365.25
# placeholder for soil object
soiln_profile = None
class StateVariables(StatesTemplate):
AGE0 = Instance(np.ndarray) # Initial age of material (d)
AGE = Instance(np.ndarray) # Appearant age of material (d)
ORGMAT = Instance(np.ndarray) # Amount of organic matter (kg ORG ha-1)
CORG = Instance(np.ndarray) # Amount of C in organic matter (kg C ha-1)
NORG = Instance(np.ndarray) #
NH4 = Instance(np.ndarray) # Amount of NH4-N (kg N ha-1)
NO3 = Instance(np.ndarray) # Amount of NO3-N (kg N ha-1)
NAVAIL = Float() # total mineral N from soil and fertiliser kg N ha-1
NDENITCUM = Float()
NO3LEACHCUM = Float()
NH4LEACHCUM = Float()
NLOSSCUM = Float()
RORGMATDISTT = Float()
RORGMATAMTT = Float()
RCORGDISTT = Float()
RCORGAMTT = Float()
RNORGDISTT = Float()
RNORGAMTT = Float()
RNO3NITRTT = Float()
RNO3DENITRTT = Float()
RNO3UPTT = Float()
RNO3INTT = Float()
RNO3OUTTT = Float()
RNO3AMTT = Float()
RNO3DEPOSTT = Float()
RNH4MINTT = Float()
RNH4NITRTT = Float()
RNH4UPTT = Float()
RNH4INTT = Float()
RNH4OUTTT = Float()
RNH4AMTT = Float()
RNH4DEPOSTT = Float()
ORGMATT = Float()
CORGT = Float()
NORGT = Float()
RMINT = Float()
NH4T = Float()
NO3T = Float()
class RateVariables(RatesTemplate):
RAGE = Instance(np.ndarray)
RORGMAT = Instance(np.ndarray)
RCORG = Instance(np.ndarray)
RNORG = Instance(np.ndarray)
RAGEAG = Instance(np.ndarray)
RORGMATDIS = Instance(np.ndarray)
RCORGDIS = Instance(np.ndarray)
RNORGDIS = Instance(np.ndarray)
RAGEAM = Instance(np.ndarray)
RORGMATAM = Instance(np.ndarray)
RCORGAM = Instance(np.ndarray)
RNORGAM = Instance(np.ndarray)
RNH4 = Instance(np.ndarray)
RNH4MIN = Instance(np.ndarray)
RNH4NITR = Instance(np.ndarray)
RNH4UP = Instance(np.ndarray)
RNH4IN = Instance(np.ndarray)
RNH4OUT = Instance(np.ndarray)
RNH4AM = Instance(np.ndarray)
RNH4DEPOS = Instance(np.ndarray)
RNO3 = Instance(np.ndarray)
RNO3NITR = Instance(np.ndarray)
RNO3DENITR = Instance(np.ndarray)
RNO3UP = Instance(np.ndarray)
RNO3IN = Instance(np.ndarray)
RNO3OUT = Instance(np.ndarray)
RNO3AM = Instance(np.ndarray)
RNO3DEPOS = Instance(np.ndarray)
RNH4LEACHCUM = Float()
RNO3LEACHCUM = Float()
RNDENITCUM = Float()
RNLOSS = Float()
class Parameters(ParamTemplate):
A0SOM = Float() # Initial age of humus (y)
CNRatioBio = Float() # C:N ratio of microbial biomass (kg C kg-1 N)
FASDIS = Float() # Fraction of assimilation to dissimilation (kg ORG kg-1 ORG)
KDENIT_REF = Float() # Reference first order denitrification rate constant (d-1)
KNIT_REF = Float() # Reference first order nitrification rate constant (d-1)
KSORP = Float() # Sorption coefficient ammonium (m3 water kg-1 soil)
MRCDIS = Float() # Michaelis Menten constant for response factor denitrification to soil respiration
NO3ConcR = Float() # NO3-N concentration in rain water (mg N L-1)
NH4ConcR = Float() # NH4-N concentration in rain water (mg N L-1)
NO3I = Instance(list) # Initial amount of NO3-N (kg N ha-1)
NH4I = Instance(list) # Initial amount of NH4-N (kg N ha-1)
WFPS_CRIT = Float() # Critical water filled pore space fraction (m3 water m-3 pore) for denitrification
def initialize(self, day, kiosk, parvalues):
self.kiosk = kiosk
self.params = self.Parameters(parvalues)
if "soil_profile" not in parvalues:
msg = "Cannot find 'soil_profile' object in `parvalues`. The 'soil_profile' object should be " \
"instantiated by the multi-layer waterbalance before SNOMIN can run. It looks like SNOMIN " \
"was started before the waterbalance."
raise exc.PCSEError(msg)
self.soiln_profile = parvalues["soil_profile"]
# Initialize module
sinm = self.SoilInorganicNModel()
# Initialize state variables
# parvalues._soildata["soil_profile"] = self.soiln_profile
NH4 = np.zeros(len(self.soiln_profile))
NO3 = np.zeros_like(NH4)
AGE = np.zeros((1, len(self.soiln_profile)))
AGE0 = np.zeros_like(AGE)
ORGMAT = np.zeros_like(AGE)
CORG = np.zeros_like(AGE)
NORG = np.zeros_like(AGE)
minip_C = self.SoilOrganicNModel.MINIP_C()
for il, layer in enumerate(self.soiln_profile):
NH4[il] = self.params.NH4I[il] * self.m2_to_ha
NO3[il] = self.params.NO3I[il] * self.m2_to_ha
AGE0[0,il] = self.params.A0SOM * self.y_to_d
AGE[0,il] = self.params.A0SOM * self.y_to_d
ORGMAT[0,il] = layer.RHOD_kg_per_m3 * layer.FSOMI * layer.Thickness_m
CORG[0,il] = minip_C.calculate_organic_C(ORGMAT[0,il])
NORG[0,il] = CORG[0, il] / layer.CNRatioSOMI
states = dict(
NH4 = NH4,
NO3 = NO3,
AGE = AGE,
AGE0 = AGE0,
ORGMAT = ORGMAT,
CORG = CORG,
NORG = NORG,
# Initialize state variables to check the mass balance of organic matter
RORGMATDISTT = 0.,
RORGMATAMTT = 0.,
# Initialize state variables to check the mass balance of organic C
RCORGDISTT = 0.,
RCORGAMTT = 0.,
# Initialize state variables to check the mass balance of organic N
RNORGDISTT = 0.,
RNORGAMTT = 0.,
# Initialize state variables to check the mass balance of NH4-N
RNH4MINTT = 0.,
RNH4NITRTT = 0.,
RNH4UPTT = 0.,
RNH4INTT = 0.,
RNH4OUTTT = 0.,
RNH4AMTT = 0.,
RNH4DEPOSTT = 0.,
# Initialize state variables to check the mass balance of NO3-N
RNO3NITRTT = 0.,
RNO3DENITRTT = 0.,
RNO3UPTT = 0.,
RNO3INTT = 0.,
RNO3OUTTT = 0.,
RNO3AMTT = 0.,
RNO3DEPOSTT = 0.,
# Initialize output variables
CORGT = np.sum(CORG) / self.m2_to_ha,
NORGT = np.sum(NORG) / self.m2_to_ha,
ORGMATT = np.sum(ORGMAT) / self.m2_to_ha,
RMINT = 0.,
NH4T = np.sum(NH4)/ self.m2_to_ha,
NO3T = np.sum(NO3)/ self.m2_to_ha,
NAVAIL = 0.,
NH4LEACHCUM = 0.,
NO3LEACHCUM = 0.,
NDENITCUM = 0.,
NLOSSCUM = 0.,
)
self.states = self.StateVariables(kiosk, publish=["NAVAIL", "ORGMATT", "CORGT", "NORGT"], **states)
self.rates = self.RateVariables(kiosk)
self._RAGEAM = np.zeros_like(AGE)
self._RORGMATAM = np.zeros_like(ORGMAT)
self._RCORGAM = np.zeros_like(CORG)
self._RNORGAM = np.zeros_like(NORG)
self._RNH4AM = np.zeros_like(NH4)
self._RNO3AM = np.zeros_like(NO3)
self._ORGMATI = ORGMAT
self._CORGI = CORG
self._NORGI = NORG
self._NH4I = NH4
self._NO3I = NO3
# Connect module to signal AgroManager
self._connect_signal(self._on_APPLY_N_SNOMIN, signals.apply_n_snomin)
@prepare_rates
def calc_rates(self, day, drv):
k = self.kiosk
r = self.rates
s = self.states
p = self.params
delt = 1.0
# Initialize model components
sonm = self.SoilOrganicNModel()
sinm = self.SoilInorganicNModel()
# Get external variables in the right unit
infiltration_rate_m_per_d = self.get_infiltration_rate(k)
flow_m_per_d = self.get_water_flow_rates(k)
N_demand_soil = self.get_N_demand(k)
RD_m = self.get_root_length(k)
SM = self.get_soil_moisture_content(k)
pF = self.get_pF(self.soiln_profile, SM)
T = drv.TEMP
# Get soil pH
pH = self.get_pH(self.soiln_profile)
# Collect ammendment rates
r.RAGEAM = self._RAGEAM
r.RORGMATAM = self._RORGMATAM
r.RCORGAM = self._RCORGAM
r.RNORGAM = self._RNORGAM
r.RNH4AM = self._RNH4AM
r.RNO3AM = self._RNO3AM
# Reset placeholders for ammendment rates
self._RAGEAM = np.zeros_like(s.AGE)
self._RORGMATAM = np.zeros_like(r.RORGMATAM)
self._RCORGAM = np.zeros_like(r.RCORGAM)
self._RNORGAM = np.zeros_like(r.RNORGAM)
self._RNH4AM = np.zeros_like(r.RNH4)
self._RNO3AM = np.zeros_like(r.RNH4)
# Calculate increase aparent age of each ammendment
r.RAGEAG = sonm.calculate_apparent_age_increase_rate(s.AGE, delt, pF, pH, T)
# Calculate dissimilation rates of each ammendment
r.RORGMATDIS, r.RCORGDIS, r.RNORGDIS = \
sonm.calculate_dissimilation_rates(s.AGE, p.CNRatioBio, p.FASDIS, s.NORG, s.ORGMAT, pF, pH, T)
# Calculate rates of change apparent age, organic matter, organic C, and organic N
r.RAGE = r.RAGEAG + r.RAGEAM
r.RORGMAT = r.RORGMATAM - r.RORGMATDIS
r.RCORG = r.RCORGAM - r.RCORGDIS
r.RNORG = r.RNORGAM - r.RNORGDIS
# Calculate N uptake rates
r.RNH4UP, r.RNO3UP = sinm.calculate_N_uptake_rates(self.soiln_profile, delt, p.KSORP, N_demand_soil,
s.NH4, s.NO3, RD_m, SM)
# Calculate remaining amounts of NH4-N and NO3-N after uptake and calculate chemical conversion
NH4PRE = s.NH4 - r.RNH4UP * delt
NO3PRE = s.NO3 - r.RNO3UP * delt
r.RNH4MIN, r.RNH4NITR, r.RNO3NITR, r.RNO3DENITR = \
sinm.calculate_reaction_rates(self.soiln_profile, p.KDENIT_REF, p.KNIT_REF, p.KSORP, p.MRCDIS,
NH4PRE, NO3PRE, r.RCORGDIS, r.RNORGDIS, SM, T, p.WFPS_CRIT)
# For each layer: if the net mineralization rate is negative (net immobilization) and there is not enough
# NH4-N in the layer to be taken up by the organic matter, only the amount of N that remains after
# denitrification is available for immobilization. RNORGDIS is updated as well to close the N balance again.
for il, layer in enumerate(self.soiln_profile):
if NH4PRE[il] + (r.RNH4MIN[il] - r.RNH4NITR[il]) * delt < 0:
r.RNH4MIN[il] = NH4PRE[il] - r.RNH4NITR[il]
RNORDIST = r.RNORGDIS[:,il].sum()
for iam in range(0,len(r.RNORGDIS[:,il])):
r.RNORGDIS[iam,il] = (r.RNH4MIN[il] / RNORDIST) * r.RNORGDIS[iam,il]
r.RNORG = r.RNORGAM - r.RNORGDIS
# Calculate deposition rates
r.RNH4DEPOS, r.RNO3DEPOS = sinm.calculate_deposition_rates(self.soiln_profile, infiltration_rate_m_per_d,
s.NH4, p.NH4ConcR, s.NO3, p.NO3ConcR)
# Calculate remaining amounts of NH4-N and NO3-N after uptake and reaction and calculate inorganic
# N flow rates between layers
NH4PRE2 = NH4PRE + (r.RNH4AM + r.RNH4MIN + r.RNH4DEPOS - r.RNH4NITR) * delt
NO3PRE2 = NO3PRE + (r.RNO3AM + r.RNO3NITR + r.RNO3DEPOS - r.RNO3DENITR) * delt
r.RNH4IN, r.RNH4OUT, r.RNO3IN, r.RNO3OUT = \
sinm.calculate_flow_rates(self.soiln_profile, flow_m_per_d, p.KSORP, NH4PRE2, NO3PRE2, SM)
# Calculate rates of change NH4-N and NO3-N
r.RNH4 = r.RNH4AM + r.RNH4MIN + r.RNH4DEPOS - r.RNH4NITR - r.RNH4UP + r.RNH4IN - r.RNH4OUT
r.RNO3 = r.RNO3AM + r.RNO3NITR + r.RNO3DEPOS - r.RNO3DENITR - r.RNO3UP + r.RNO3IN - r.RNO3OUT
# Calculate output rate variables for N loss
r.RNH4LEACHCUM = (1/self.m2_to_ha) * r.RNH4OUT[-1]
r.RNO3LEACHCUM = (1/self.m2_to_ha) * r.RNO3OUT[-1]
r.RNDENITCUM = (1/self.m2_to_ha) * r.RNO3DENITR.sum()
@prepare_states
def integrate(self, day, delt=1.0):
k = self.kiosk
p = self.params
r = self.rates
s = self.states
# Initialize soil module
sinm = self.SoilInorganicNModel()
# Calculate apparent ages and amounts of organic matter, organic C, and organic N in next time step
AGE = s.AGE + r.RAGE * delt
ORGMAT = s.ORGMAT + r.RORGMAT * delt
CORG = s.CORG + r.RCORG * delt
NORG= s.NORG + r.RNORG * delt
# Calculate amounts of NH4-N and NO3-N in next time step
NH4 = s.NH4 + r.RNH4 * delt
NO3 = s.NO3 + r.RNO3 * delt
# Update state variables
s.AGE = AGE
s.ORGMAT = ORGMAT
s.CORG = CORG
s.NORG = NORG
s.NH4 = NH4
s.NO3 = NO3
# Get external state variable
RD_m = self.get_root_length(k)
SM = self.get_soil_moisture_content(k)
# Calculate the amount of N available for root uptake in the next time step
s.NAVAIL = sinm.calculate_NAVAIL(self.soiln_profile, p.KSORP, s.NH4, s.NO3, RD_m, SM) / self.m2_to_ha
self.check_mass_balances(day, delt)
# Set output variables
s.ORGMATT = np.sum(s.ORGMAT) * (1/self.m2_to_ha)
s.CORGT = np.sum(s.CORG) * (1/self.m2_to_ha)
s.NORGT = np.sum(s.NORG) * (1/self.m2_to_ha)
s.RMINT += np.sum(r.RNORGDIS) * (1/self.m2_to_ha)
s.NH4T = np.sum(s.NH4) * (1/self.m2_to_ha)
s.NO3T = np.sum(s.NO3) * (1/self.m2_to_ha)
NH4LEACHCUM = s.NH4LEACHCUM + r.RNH4LEACHCUM * delt
NO3LEACHCUM = s.NO3LEACHCUM + r.RNO3LEACHCUM * delt
NDENITCUM = s.NDENITCUM + r.RNDENITCUM * delt
NLOSSCUM = NH4LEACHCUM + NO3LEACHCUM + NDENITCUM
s.NH4LEACHCUM = NH4LEACHCUM
s.NO3LEACHCUM = NO3LEACHCUM
s.NDENITCUM = NDENITCUM
s.NLOSSCUM = NLOSSCUM
def _on_APPLY_N_SNOMIN(self, amount=None, application_depth = None, cnratio=None, f_orgmat=None,
f_NH4N = None, f_NO3N = None, initial_age =None):
"""This function calculates the application rates of organic matter, organic C, organic N, NH4-N, NO3-N
and the initial apparent age of the applied material at the date of application.
For each amendment, the following variables need to be provided in the AgroManagement
file of the simulation:
**Amendment properties**
================== ====================================================== =========================
Name Description Unit
================== ====================================================== =========================
amount Amount of material in amendment kg material ha-1
application_depth Depth over which the amendment is applied in the soil cm
cnratio C:N ratio of organic matter in material kg C kg-1 N
initial_age Initial apparent age of organic matter in material y
f_NH4N Fraction of NH4+-N in material kg NH4+-N kg-1 material
f_NO3N Fraction of NO3--N in material kg NO3--N kg-1 material
f_orgmat Fraction of organic matter in amendment kg OM kg-1 material
================== ====================================================== =========================
"""
r = self.rates
s = self.states
delt = 1.
# Create model components
sinm = self.SoilInorganicNModel()
sonm = self.SoilOrganicNModel()
# Initialize amendment rates
RAGE_am = np.zeros((1, len(self.soiln_profile)))
AGE0_am = np.zeros_like(RAGE_am)
RORGMAT_am = np.zeros_like(RAGE_am)
RCORG_am = np.zeros_like(RAGE_am)
RNORG_am = np.zeros_like(RAGE_am)
RNH4_am = np.zeros_like(s.NH4)
RNO3_am = np.zeros_like(s.NO3)
# Prevents that a part of the N is not applied if the application depth is less than thickness of the upper layer
if application_depth < self.soiln_profile[0].Thickness:
application_depth = self.soiln_profile[0].Thickness
AGE0_am[0, :] = initial_age * self.y_to_d
RAGE_am[0, :] = initial_age * self.y_to_d
RNH4_am, RNO3_am = np.array(sinm.calculate_N_application_amounts(self.soiln_profile, amount, application_depth,
f_NH4N, f_NO3N)) * self.m2_to_ha
RORGMAT_am, RCORG_am, RNORG_am = np.array(sonm.calculate_application_rates(self.soiln_profile, amount,
application_depth, cnratio, f_orgmat)
) * self.m2_to_ha
# Add a new column to the state variables for organic amendments to add the new amendment.
s.AGE0 = np.concatenate((s.AGE0, AGE0_am), axis = 0)
s.ORGMAT = np.concatenate((s.ORGMAT, np.zeros((1, len(self.soiln_profile)))), axis = 0)
s.CORG = np.concatenate((s.CORG, np.zeros((1, len(self.soiln_profile)))), axis = 0)
s.NORG = np.concatenate((s.NORG, np.zeros((1, len(self.soiln_profile)))), axis = 0)
s.AGE = np.concatenate((s.AGE, np.zeros((1, len(self.soiln_profile)))), axis = 0)
# Store the amendment rates
self._RAGEAM = np.concatenate((self._RAGEAM, RAGE_am), axis = 0)
self._RORGMATAM = np.concatenate((self._RORGMATAM, RORGMAT_am), axis = 0)
self._RCORGAM = np.concatenate((self._RCORGAM, RCORG_am), axis = 0)
self._RNORGAM = np.concatenate(( self._RNORGAM, RNORG_am), axis = 0)
self._RNH4AM = RNH4_am
self._RNO3AM = RNO3_am
def get_infiltration_rate(self, k):
infiltration_rate_m_per_d = k.RIN * self.cm_to_m
return infiltration_rate_m_per_d
def get_pF(self, soiln_profile, SM):
pF = np.zeros_like(SM)
for il, layer in enumerate(soiln_profile):
pF[il] = layer.PFfromSM(SM[il])
return pF
def get_pH(self, soiln_profile):
pH = np.zeros(len(soiln_profile))
for il, layer in enumerate(soiln_profile):
pH[il] = layer.Soil_pH
return pH
def get_soil_moisture_content(self, k):
SM = k.SM
return SM
def get_water_flow_rates(self, k):
flow_m_per_d = k.Flow * self.cm_to_m
return flow_m_per_d
def get_N_demand(self, k):
if "RNuptake" in k:
N_demand_soil = k.RNuptake * self.m2_to_ha
else:
N_demand_soil = 0.
return N_demand_soil
def get_root_length(self, k):
if "RD" in k:
RD_m = k.RD * self.cm_to_m
else:
RD_m = 0.
return RD_m
def check_mass_balances(self, day, delt):
s = self.states
r = self.rates
s.RORGMATAMTT += delt * r.RORGMATAM.sum()
s.RORGMATDISTT += delt * r.RORGMATDIS.sum()
s.RCORGAMTT += delt * r.RCORGAM.sum()
s.RCORGDISTT += delt * r.RCORGDIS.sum()
s.RNORGAMTT += delt * r.RNORGAM.sum()
s.RNORGDISTT += delt * r.RNORGDIS.sum()
s.RNH4MINTT += delt * r.RNH4MIN.sum()
s.RNH4NITRTT += delt * r.RNH4NITR.sum()
s.RNH4UPTT += delt * r.RNH4UP.sum()
s.RNH4INTT += delt * r.RNH4IN.sum()
s.RNH4OUTTT += delt * r.RNH4OUT.sum()
s.RNH4AMTT += delt * r.RNH4AM.sum()
s.RNH4DEPOSTT += delt * r.RNH4DEPOS.sum()
s.RNO3NITRTT += delt * r.RNO3NITR.sum()
s.RNO3DENITRTT += delt * r.RNO3DENITR.sum()
s.RNO3UPTT += delt * r.RNO3UP.sum()
s.RNO3INTT += delt * r.RNO3IN.sum()
s.RNO3OUTTT += delt * r.RNO3OUT.sum()
s.RNO3AMTT += delt * r.RNO3AM.sum()
s.RNO3DEPOSTT += delt * r.RNO3DEPOS.sum()
ORGMATBAL = self._ORGMATI.sum() - s.ORGMAT.sum() + s.RORGMATAMTT - s.RORGMATDISTT
if abs(ORGMATBAL) > 0.0001:
msg = "Organic matter mass balance is not closing on %s with checksum: %f" % (day, ORGMATBAL)
raise exc.SoilOrganicMatterBalanceError(msg)
CORGBAL = self._CORGI.sum() - s.CORG.sum() + s.RCORGAMTT - s.RCORGDISTT
if abs(CORGBAL) > 0.0001:
msg = "Organic carbon mass balance is not closing on %s with checksum: %f" % (day, CORGBAL)
raise exc.SoilOrganicCarbonBalanceError(msg)
NORGBAL = self._NORGI.sum() - s.NORG.sum() + s.RNORGAMTT - s.RNORGDISTT
if abs(NORGBAL) > 0.0001:
msg = "Organic carbon mass balance is not closing on %s with checksum: %f" % (day, NORGBAL)
raise exc.SoilOrganicNitrogenBalanceError(msg)
NH4BAL = self._NH4I.sum() - s.NH4.sum() + s.RNH4AMTT + s.RNH4INTT + s.RNH4MINTT + s.RNH4DEPOSTT - s.RNH4NITRTT - s.RNH4OUTTT - s.RNH4UPTT
if abs(NH4BAL) > 0.0001:
msg = "NH4-N mass balance is not closing on %s with checksum: %f" % (day, NH4BAL)
raise exc.SoilAmmoniumBalanceError(msg)
NO3BAL = self._NO3I.sum() - s.NO3.sum() + s.RNO3AMTT + s.RNO3NITRTT + s.RNO3INTT + s.RNO3DEPOSTT - s.RNO3DENITRTT - s.RNO3OUTTT - s.RNO3UPTT
if abs(NO3BAL) > 0.0001:
msg = "NO3-N mass balance is not closing on %s with checksum: %f" % (day, NO3BAL)
raise exc.SoilNitrateBalanceError(msg)
class SoilInorganicNModel:
def calculate_N_application_amounts(self, soiln_profile, amount, application_depth, f_NH4N, f_NO3N):
samm = self.SoilAmmoniumNModel()
sni = self.SoilNNitrateModel()
RNH4_am = np.zeros(len(soiln_profile))
RNO3_am = np.zeros_like(RNH4_am)
zmin = 0
for il, layer in enumerate(soiln_profile):
zmax = zmin + soiln_profile[il].Thickness
RNH4_am[il] = samm.calculate_NH4_application_amount(amount, application_depth, f_NH4N, layer.Thickness, zmax, zmin)
RNO3_am[il] = sni.calculate_NO3_application_amount(amount, application_depth, f_NO3N, layer.Thickness, zmax, zmin)
zmin = zmax
return RNH4_am, RNO3_am
def calculate_flow_rates(self, soiln_profile, flow_m_per_d, KSORP, NH4, NO3, SM):
samm = self.SoilAmmoniumNModel()
sni = self.SoilNNitrateModel()
RNH4IN, RNH4OUT = samm.calculate_NH4_flow_rates(soiln_profile, flow_m_per_d, KSORP, NH4, SM)
RNO3IN, RNO3OUT = sni.calculate_NO3_flow_rates(soiln_profile, flow_m_per_d, NO3, SM)
return RNH4IN, RNH4OUT, RNO3IN, RNO3OUT
def calculate_deposition_rates(self,soiln_profile,infiltration_rate_m_per_d, NH4, NH4ConcR, NO3, NO3ConcR):
samm = self.SoilAmmoniumNModel()
sni = self.SoilNNitrateModel()
RNH4DEPOS = samm.calculate_NH4_deposition_rates(soiln_profile, infiltration_rate_m_per_d, NH4, NH4ConcR)
RNO3DEPOS = sni.calculate_NO3_deposition_rates(soiln_profile, infiltration_rate_m_per_d, NO3, NO3ConcR)
return RNH4DEPOS, RNO3DEPOS
def calculate_reaction_rates(self, soiln_profile, KDENIT_REF, KNIT_REF, KSORP, MRCDIS, NH4, NO3, RCORGDIS, RNORGDIS, SM, T, WFPS_CRIT):
samm = self.SoilAmmoniumNModel()
sni = self.SoilNNitrateModel()
RNH4MIN, RNH4NITR = samm.calculate_NH4_reaction_rates(soiln_profile, KNIT_REF, KSORP, NH4, RNORGDIS, SM, T)
RNO3NITR, RNO3DENITR = sni.calculate_NO3_reaction_rates(soiln_profile, KDENIT_REF, MRCDIS, NO3, RCORGDIS, RNH4NITR, SM, T, WFPS_CRIT)
return RNH4MIN, RNH4NITR, RNO3NITR, RNO3DENITR
def calculate_NAVAIL(self, soiln_profile, KSORP, NH4, NO3, RD_m, SM):
samm = self.SoilAmmoniumNModel()
sni = self.SoilNNitrateModel()
zmin = 0.
NAVAIL = 0.
for il, layer in enumerate(soiln_profile):
zmax = zmin + layer.Thickness_m
NH4_avail_layer = samm.calculate_available_NH4(KSORP, NH4[il], RD_m, layer.RHOD_kg_per_m3, SM[il], zmax, zmin)
NO3_avail_layer = sni.calculate_available_NO3(NO3[il], RD_m, SM[il], zmax, zmin)
NAVAIL += (NH4_avail_layer + NO3_avail_layer)
zmin = zmax
return NAVAIL
def calculate_N_uptake_rates(self, soiln_profile, delt, KSORP, N_demand_soil, NH4, NO3, RD_m, SM):
RNH4UP = np.zeros_like(NH4)
RNO3UP = np.zeros_like(NO3)
samm = self.SoilAmmoniumNModel()
sni = self.SoilNNitrateModel()
zmin = 0.
for il, layer in enumerate(soiln_profile):
zmax = zmin + layer.Thickness_m
RNH4UP[il] = samm.calculate_NH4_plant_uptake_rate(KSORP, N_demand_soil, NH4[il], RD_m, layer.RHOD_kg_per_m3, SM[il], zmax, zmin)
N_demand_soil -= RNH4UP[il] * delt
RNO3UP[il] = sni.calculate_NO3_plant_uptake_rate(N_demand_soil, NO3[il], RD_m, SM[il], zmax, zmin)
N_demand_soil -= RNO3UP[il] * delt
zmin = zmax
return RNH4UP, RNO3UP
class SoilAmmoniumNModel:
def calculate_NH4_deposition_rates(self, soiln_profile, infiltration_rate_m_per_d, NH4, NH4ConcR):
RNH4DEPOS = np.zeros_like(NH4)
mg_to_kg = 1e-6
L_to_m3 = 1e-3
for il, layer in enumerate(soiln_profile):
if il == 0:
RNH4DEPOS[il] = (mg_to_kg / L_to_m3) * NH4ConcR * infiltration_rate_m_per_d
else:
RNH4DEPOS[il] = 0.
return RNH4DEPOS
def calculate_NH4_reaction_rates(self, soiln_profile, KNITREF, KSORP, NH4, RNORGDIS, SM, T):
RNH4MIN = np.zeros_like(NH4)
RNH4NITR = np.zeros_like(NH4)
for il, layer in enumerate(soiln_profile):
RNMIN_kg_per_m2 = RNORGDIS[:,il].sum()
RNH4MIN[il] = self.calculate_mineralization_rate(RNMIN_kg_per_m2)
RNH4NITR[il] = self.calculate_nitrification_rate(KNITREF, KSORP, layer.Thickness_m, NH4[il], layer.RHOD_kg_per_m3, SM[il], layer.SM0, T)
return RNH4MIN, RNH4NITR
def calculate_NH4_flow_rates(self, soiln_profile, flow_m_per_d, KSORP, NH4, SM):
cNH4Kwel = 0.
RNH4IN = np.zeros_like(NH4)
RNH4OUT = np.zeros_like(NH4)
RHOD = np.zeros_like(NH4)
cNH4 = np.zeros_like(NH4)
dz = np.zeros_like(NH4)
# Downward flow
for il, layer in enumerate(soiln_profile):
RHOD[il] = layer.RHOD_kg_per_m3
dz[il] = layer.Thickness_m
cNH4[il] = self.calculate_NH4_concentration(KSORP, dz[il], NH4[il], RHOD[il], SM[il])
for il in range(0,len(soiln_profile)):
if flow_m_per_d[il] >= 0.:
if il == 0:
RNH4IN[il] += 0.
else:
RNH4IN[il] += flow_m_per_d[il] * cNH4[il - 1]
RNH4OUT[il-1] += flow_m_per_d[il] * cNH4[il - 1]
if flow_m_per_d[len(NH4) - 1] >= 0.:
RNH4OUT[len(NH4) - 1] += flow_m_per_d[len(NH4)] * cNH4[len(NH4) - 1]
## Upward flow
for il in reversed(range(0,len(soiln_profile))):
if flow_m_per_d[il + 1] < 0.:
if il == len(NH4) - 1:
RNH4IN[il] += - flow_m_per_d[il + 1] * cNH4Kwel
else:
RNH4IN[il] += - flow_m_per_d[il + 1] * cNH4[il + 1]
RNH4OUT[il + 1] += - flow_m_per_d[il + 1] * cNH4[il + 1]
if flow_m_per_d[0] < 0.:
RNH4OUT[0] += - flow_m_per_d[0] * cNH4[0]
else:
RNH4OUT[0] += 0.
return RNH4IN, RNH4OUT
def calculate_NH4_application_amount(self, amount, application_depth, f_NH4N, layer_thickness, zmax, zmin):
if application_depth > zmax:
NH4_am = (layer_thickness / application_depth) * f_NH4N * amount
elif zmin <= application_depth <= zmax:
NH4_am = ((application_depth - zmin) / application_depth) * f_NH4N * amount
else:
NH4_am = 0.
return NH4_am
def calculate_NH4_concentration(self, KSORP, layer_thickness, NH4, RHOD_kg_per_m3, SM):
cNH4 = (1 / ( KSORP * RHOD_kg_per_m3 + SM)) * NH4 / layer_thickness
return cNH4
def calculate_available_NH4(self, KSORP, NH4, RD, RHOD_kg_per_m3, SM, zmax, zmin):
layer_thickness = zmax - zmin
cNH4 = self.calculate_NH4_concentration(KSORP, layer_thickness, NH4, RHOD_kg_per_m3, SM)
if RD <= zmin:
NH4_avail = 0.
elif RD > zmax:
NH4_avail = (SM / ( KSORP * RHOD_kg_per_m3 + SM)) * NH4
else:
NH4_avail = ((RD - zmin)/ layer_thickness) * (SM / ( KSORP * RHOD_kg_per_m3 + SM)) * NH4
return NH4_avail
def calculate_mineralization_rate(self, rNMINs_layer):
RNH4MIN = rNMINs_layer.sum()
return RNH4MIN
def calculate_nitrification_rate(self, KNIT_REF, KSORP, layer_thickness, NH4, RHOD_kg_per_m3, SM, SM0, T):
cNH4 = self.calculate_NH4_concentration(KSORP, layer_thickness, NH4, RHOD_kg_per_m3, SM)
fWNIT = self.calculate_soil_moisture_response_nitrification_rate_constant(SM, SM0)
fT = self.calculate_temperature_response_nitrification_rate_constant(T)
RNH4NIT = fWNIT * fT * KNIT_REF * SM * cNH4 * layer_thickness
return RNH4NIT
def calculate_NH4_plant_uptake_rate(self, KSORP, N_demand_soil, NH4, RD_m, RHOD_kg_per_m3, SM, zmax, zmin):
NH4_av = self.calculate_available_NH4(KSORP, NH4, RD_m, RHOD_kg_per_m3, SM, zmax, zmin)
RNH4UP = min(N_demand_soil, NH4_av)
return RNH4UP
def calculate_soil_moisture_response_nitrification_rate_constant(self, SM, SM0):
WFPS = SM / SM0
fWNIT = 0.9 / (1. + np.exp(-15 *(WFPS - 0.45))) + 0.1 - 1/(1+np.exp(-50. * (WFPS - 0.95)))
return fWNIT
def calculate_temperature_response_nitrification_rate_constant(self, T):
fT = 1/(1+np.exp(-0.26*(T-17.)))-1/(1+np.exp(-0.77*(T-41.9)))
return fT
class SoilNNitrateModel:
def calculate_NO3_deposition_rates(self, soiln_profile, infiltration_rate_m_per_d, NO3, NO3ConcR):
mg_to_kg = 1e-6
L_to_m3 = 1e-3
RNO3DEPOS = np.zeros_like(NO3)
for il, layer in enumerate(soiln_profile):
if il == 0:
RNO3DEPOS[il] = (mg_to_kg / L_to_m3) * NO3ConcR * infiltration_rate_m_per_d
else:
RNO3DEPOS[il] = 0.
return RNO3DEPOS
def calculate_NO3_flow_rates(self, soiln_profile, flow_m_per_d, NO3, SM):
cNO3Kwel = 0.
RNO3IN = np.zeros_like(NO3)
RNO3OUT = np.zeros_like(NO3)
cNO3 = np.zeros_like(NO3)
dz = np.zeros_like(NO3)
# Downward flow
for il, layer in enumerate(soiln_profile):
dz[il] = layer.Thickness_m
cNO3[il] = self.calculate_NO3_concentration(dz[il], NO3[il], SM[il])
for il in range(0,len(soiln_profile)):
if flow_m_per_d[il] >= 0.:
if il == 0:
RNO3IN[il] += 0.
else:
RNO3IN[il] += flow_m_per_d[il] * cNO3[il - 1]
RNO3OUT[il-1] += flow_m_per_d[il] * cNO3[il - 1]
if flow_m_per_d[len(NO3) - 1] >= 0.:
RNO3OUT[len(NO3) - 1] += flow_m_per_d[len(NO3)] * cNO3[len(NO3) - 1]
else:
RNO3OUT[len(NO3) - 1] += 0
# Upward flow
for il in reversed(range(0,len(soiln_profile))):
if flow_m_per_d[il + 1] < 0.:
if il == len(NO3) - 1:
RNO3IN[il] += - flow_m_per_d[il + 1] * cNO3Kwel
else:
RNO3IN[il] += - flow_m_per_d[il + 1] * cNO3[il + 1]
RNO3OUT[il + 1] += - flow_m_per_d[il + 1] * cNO3[il + 1]
if flow_m_per_d[0] < 0.:
RNO3OUT[0] += - flow_m_per_d[0] * cNO3[0]
else:
RNO3OUT[0] += 0.
return RNO3IN, RNO3OUT
def calculate_NO3_reaction_rates(self, soiln_profile, KDENIT_REF, MRCDIS, NO3, RCORGDIS,
RNH4NITR, SM, T, WFPS_CRIT):
RNO3NITR = np.zeros_like(NO3)
RNO3DENITR = np.zeros_like(NO3)
for il, layer in enumerate(soiln_profile):
RNO3NITR[il] = RNH4NITR[il]
RNO3DENITR[il] = \
self.calculate_denitrification_rate(layer.Thickness_m, NO3[il], KDENIT_REF, MRCDIS,
RCORGDIS[:,il].sum(), SM[il], layer.SM0, T, WFPS_CRIT)
return RNO3NITR, RNO3DENITR
def calculate_NO3_application_amount(self, amount, application_depth, f_NO3N, layer_thickness, zmax, zmin):
if application_depth > zmax:
NO3_am = (layer_thickness / application_depth) * f_NO3N * amount
elif zmin <= application_depth <= zmax:
NO3_am = ((application_depth - zmin) / application_depth) * f_NO3N * amount
else:
NO3_am = 0.
return NO3_am
def calculate_NO3_concentration(self, layer_thickness, NO3, SM):
cNO3 = NO3 / (layer_thickness * SM)
return cNO3
def calculate_available_NO3(self, NO3, RD, SM, zmax, zmin):
layer_thickness = zmax - zmin
if RD <= zmin:
NO3_avail_layer = 0.
elif RD > zmax:
NO3_avail_layer = NO3
else:
NO3_avail_layer = ((RD - zmin)/ layer_thickness) * NO3
return NO3_avail_layer
def calculate_denitrification_rate(self, layer_thickness, NO3, KDENIT_REF, MRCDIS, RCORGT_kg_per_m2,
SM, SM0, T, WFPS_CRIT):
cNO3 = self.calculate_NO3_concentration(layer_thickness, NO3, SM)
fR = self.calculate_soil_respiration_response_denitrifiation_rate_constant(RCORGT_kg_per_m2, MRCDIS)
fW = self.calculate_soil_moisture_response_denitrification_rate_constant(SM, SM0, WFPS_CRIT)
fT = self.calculate_temperature_response_denitrification_rate_constant(T)
RNO3DENIT = fW * fT * fR * KDENIT_REF * SM * cNO3 * layer_thickness
return RNO3DENIT
def calculate_NO3_plant_uptake_rate(self, N_demand_soil, NO3, RD_m, SM, zmax, zmin):
NO3_av = self.calculate_available_NO3(NO3, RD_m, SM, zmax, zmin)
RNO3UP = min(N_demand_soil, NO3_av)
return RNO3UP
def calculate_soil_moisture_response_denitrification_rate_constant(self, SM, SM0, WFPS_CRIT):
WFPS = SM / SM0
if WFPS < WFPS_CRIT:
fW = 0.
else:
fW = np.power((WFPS - WFPS_CRIT)/(1 - WFPS_CRIT),2)
return fW
def calculate_soil_respiration_response_denitrifiation_rate_constant(self, RCORGT, MRCDIS):
fR = RCORGT / (MRCDIS + RCORGT)
return fR
def calculate_temperature_response_denitrification_rate_constant(self, T):
fT = 1/(1+np.exp(-0.26*(T-17)))-1/(1+np.exp(-0.77*(T-41.9)))
return fT
class SoilOrganicNModel:
def calculate_apparent_age_increase_rate(self, AGE, delt, pF, pH, T):
RAGEAG = np.zeros_like(AGE)
janssen = self.Janssen()
for am in range(0, AGE.shape[0]):
for il in range(0, AGE.shape[1]):
RAGEAG[am,il] = janssen.calculate_increase_apparent_age_rate(delt, pF[il], pH[il], T)
return RAGEAG
def calculate_application_rates(self, soiln_profile, amount, application_depth, cnratio, f_orgmat):
RORGMAT_am = np.zeros((1, len(soiln_profile)))
RCORG_am = np.zeros_like(RORGMAT_am)
RNORG_am = np.zeros_like(RORGMAT_am)
zmin = 0.
for il, layer in enumerate(soiln_profile):
zmax = zmin + soiln_profile[il].Thickness
RORGMAT_am[0, il] = \
self.calculate_organic_material_application_amount(amount, application_depth, f_orgmat,
layer.Thickness, zmax, zmin)
RCORG_am[0, il] = \
self.calculate_organic_carbon_application_amount(amount, application_depth, f_orgmat,
layer.Thickness, zmax, zmin)
RNORG_am[0, il] = \
self.calculate_organic_nitrogen_application_amount(amount, application_depth, cnratio, f_orgmat,
layer.Thickness, zmax, zmin)
zmin = zmax
return RORGMAT_am, RCORG_am, RNORG_am
def calculate_dissimilation_rates(self, AGE, CNRatioBio, FASDIS, NORG, ORGMAT, pF, pH, T):
RORGMATDIS = np.zeros_like(AGE)
RCORGDIS = np.zeros_like(AGE)
RNORGDIS = np.zeros_like(AGE)
janssen = self.Janssen()
minip_c = self.MINIP_C()
minip_n = self.MINIP_N()
for am in range(0, AGE.shape[0]):
for il in range(0, AGE.shape[1]):
if ORGMAT[am, il] > 0:
RORGMATDIS[am,il] = \
janssen.calculate_dissimilation_rate_OM_T(ORGMAT[am,il], AGE[am,il], pF[il], pH[il], T)
RCORGDIS[am,il] = \
minip_c.calculate_dissimilation_rate_C(janssen, ORGMAT[am,il], AGE[am,il], pF[il],
pH[il], T)
RNORGDIS[am,il] = \
minip_n.calculate_dissimilation_rate_N(janssen, minip_c, ORGMAT[am,il], NORG[am,il],
FASDIS, CNRatioBio, AGE[am,il], pF[il], pH[il], T)
else:
RORGMATDIS[am,il] = 0.
RCORGDIS[am,il] = 0.
RNORGDIS[am,il] = 0.
return RORGMATDIS, RCORGDIS, RNORGDIS
def calculate_organic_carbon_application_amount(self, amount, application_depth, f_orgmat, layer_thickness, zmax, zmin):
minip_C = self.MINIP_C()
ORGMAT_am = \
self.calculate_organic_material_application_amount(amount, application_depth, f_orgmat,
layer_thickness, zmax, zmin)
CORG_am = minip_C.calculate_organic_C(ORGMAT_am)
return CORG_am
def calculate_organic_nitrogen_application_amount(self, amount, application_depth, cnratio, f_orgmat, layer_thickness, zmax, zmin):
minip_C = self.MINIP_C()
ORGMAT_am = self.calculate_organic_material_application_amount(amount, application_depth, f_orgmat, layer_thickness, zmax, zmin)
CORG_am = minip_C.calculate_organic_C(ORGMAT_am)
if cnratio == 0:
NORG_am = 0.
else:
NORG_am = CORG_am / cnratio
return NORG_am
def calculate_organic_material_application_amount(self, amount, application_depth, f_orgmat, layer_thickness, zmax, zmin):
if application_depth > zmax:
ORGMAT_am = (layer_thickness / application_depth) * f_orgmat * amount
elif zmin <= application_depth <= zmax:
ORGMAT_am = ((application_depth - zmin) / application_depth) * f_orgmat * amount
else:
ORGMAT_am = 0
return ORGMAT_am
class Janssen:
m = 1.6
b = 2.82
y_to_d = 365.25
def calculate_increase_apparent_age_rate(self, dt, pF, pH, T):
f_pH = self.calculate_pH_response_dissimilation_rate(pH)
f_T = self.calculate_temperature_response_dissimilation_rate_Yang(T)
f_SM = self.calculate_soil_moisture_response_dissimilation_rate(pF)
dA = f_pH * f_T * f_SM * dt
return dA
def calculate_relative_dissimilation_rate_OM_T(self, t, pF, pH, T):
m = self.m
b = self.b
f_pH = self.calculate_pH_response_dissimilation_rate(pH)
f_T = self.calculate_temperature_response_dissimilation_rate_Yang(T)
f_SM = self.calculate_soil_moisture_response_dissimilation_rate(pF)
k = f_pH * f_T * f_SM * b * pow(t/self.y_to_d, -m) / self.y_to_d
return k
def calculate_dissimilation_rate_OM_T(self, OM, t, pF, pH, T):
k = self.calculate_relative_dissimilation_rate_OM_T(t, pF, pH, T)
rate = k * OM
return rate
def calculate_soil_moisture_response_dissimilation_rate(self, pF):
if pF < 2.7:
f_SM = 1.0
elif pF < 4.2:
f_SM = 1.0 * (4.2 - pF) / (4.2 - 2.7)
else:
f_SM = 0.
return f_SM
def calculate_pH_response_dissimilation_rate(self, pH):
f_pH = 1 / (1 + np.exp(-1.5 * (pH - 4)))
return f_pH
def calculate_temperature_response_dissimilation_rate(self, T):
f_T = pow(2, (T-9)/9)
return f_T
def calculate_temperature_response_dissimilation_rate_Yang(self, T):
if T < -1:
f_T = 0.
elif T < 9.:
f_T = 0.09 * (T + 1)
elif T < 27.:
f_T = 0.88 * pow(2, (T-9)/9)
else:
f_T = 3.5
return f_T
class MINIP_C:
OM_to_C = 0.58
y_to_d = 365.
def calculate_assimilation_rate(self, janssen, OM, f_ass_dis, t, pF, pH, T):
r_disc = self.calculate_dissimilation_rate_C(janssen, OM, t, pF, pH, T)
r_ass = r_disc * f_ass_dis
return r_ass
def calculate_dissimilation_rate_C(self, janssen, OM, t, pF, pH, T):
k = janssen.calculate_relative_dissimilation_rate_OM_T(t, pF, pH, T)
Corg = self.calculate_organic_C(OM)
rate = k * Corg
return rate
def calculate_total_conversion_rate_C(self, janssen, OM, f_ass_dis, t, pF, pH, T):
r_dis_C = self.calculate_dissimilation_rate_C(janssen, OM, t, pF, pH, T)
r_ass_C = self.calculate_assimilation_rate(janssen, OM, f_ass_dis, t, pF, pH, T)
r_conv_C = r_dis_C + r_ass_C
return r_conv_C
def calculate_organic_C(self, OM):
Corg = OM * self.OM_to_C
return Corg
class MINIP_N:
def calculate_total_conversion_rate_N(self, janssen, minip_c, OM, Norg, f_ass_dis, t, pF, pH, T):
r_conv_C = minip_c.calculate_total_conversion_rate_C(janssen, OM, f_ass_dis, t, pF, pH, T)
C = minip_c.calculate_organic_C(OM)
r_conv_N = r_conv_C * (Norg/C)
return r_conv_N
def calculate_assimilation_rate_N(self, janssen, minip_c, OM, f_ass_dis, f_C_N_microbial, t, pF, pH, T):
r_ass_C = minip_c.calculate_assimilation_rate(janssen, OM, f_ass_dis, t, pF, pH, T)
r_ass_N = r_ass_C/f_C_N_microbial
return r_ass_N
def calculate_dissimilation_rate_N(self, janssen, minip_c, OM, Norg, f_ass_dis, f_C_N_microbial, t, pF, pH, T):
r_ass_N = self.calculate_assimilation_rate_N(janssen, minip_c, OM, f_ass_dis, f_C_N_microbial, t, pF, pH, T)
r_conv_N = self.calculate_total_conversion_rate_N(janssen, minip_c, OM, Norg, f_ass_dis, t, pF, pH, T)
r_diss_N = r_conv_N - r_ass_N
return r_diss_N