def computeNet(b, a, co, dia=gy.WMeanDiam, survival=None, weed=False): #net per tree #b - biomass, a-age, co -concentration if survival is None: survival = np.ones(len(a)) idx_seedling = [0, 1, 2, 3, 4] #np.where(dia<3.5) mass = interS(a, b / survival, k=1) dm = mass.derivative() rate = dm(a) * survival * dt if weed is False: rate[idx_seedling] = b[idx_seedling] * 0.2 * dt return np.maximum(co * rate, 0.0)
def CWTr(nLyrs, z, dz, pF, Ksat, direction='positive'): """ Returns interpolation functions sto=f(gwl) profile water storage as a function ofground water level gwl=f(sto) ground water level tra=f(gwl) transissivity Input: nLyrs number of soil layers d depth of layer midpoint dz layer thickness pF van Genuchten water retention parameters: ThetaS, ThetaR, alfa, n Ksat saturated hydraulic conductivity in m s-1. K in m/day. direction: positive or negative downwards """ #-------Parameters --------------------- z = np.array(z) dz =np.array(dz) #--------- Connection between gwl and water storage------------ if direction =='positive': print 'no positive direction available' import sys; sys.exit() gwl=np.linspace(0.,sum(dz),500) sto = [sum(wrc(pF, x = np.minimum(z-g, 0.0))*dz) for g in gwl] #equilibrium head m else: gwl=np.linspace(0.,-sum(dz),100) sto = [sum(wrc(pF, x = np.minimum(z+g, 0.0))*dz) for g in gwl] #equilibrium head m gwlabove = [2.0,1.5,0.5] stoabove = [sto[0]+0.75*gwlabove[0], sto[0]+0.3*gwlabove[1], sto[0]+0.2*gwlabove[2]] stoT=list(stoabove)+list(sto) gwlT=list(gwlabove)+list(gwl) gwlToSto = interp1d(np.array(gwlT), np.array(stoT), fill_value='extrapolate') stoT = list(stoT); gwl= list(gwlT) sto.reverse(); gwl.reverse() stoToGwl =interp1d(np.array(stoT), np.array(gwlT), fill_value='extrapolate') cc=np.gradient(gwlToSto(gwlT))/np.gradient(gwlT) # cc = np.gradient(gwlToSto(gwlT), gwlT) # Iñaki cc[cc<0.2]=0.2 # C = interp1d(np.array(gwlT), cc, bounds_error=False, fill_value=(0.,1.) ) #storage coefficient function # Iñaki's way: zeta = -z C = interp1d(zeta, wrc(pF, zeta), bounds_error=False, fill_value=(wrc(pF[-1], zeta[-1]), 1.)) #import matplotlib.pylab as plt #plt.plot(gwlT, cc, 'ro') #plt.plot(gwlT, C(gwlT), 'b-') #import sys; sys.exit() gwlToSto = interp1d(np.array(gwlT), np.array(stoT), fill_value='extrapolate') stoT = list(stoT); gwlT= list(gwlT) stoT.reverse(); gwlT.reverse() stoToGwl =interp1d(np.array(stoT), np.array(gwlT), fill_value='extrapolate') del gwlT, stoT #----------Transmissivity------------------- K = np.array(Ksat*86400.) #from m/s to m/day tr =[sum(K[t:] * dz[t:]) for t in range(nLyrs)] if direction=='positive': gwlToTra = interS(z, np.array(tr)) else: z= list(z); z.reverse(); tr.reverse() gwlToTra = interS(-np.array(z), np.array(tr), k=3, ext='const') # returns limiting value outside interpolation domain del tr return gwlToSto, K, gwlToTra, C
def fWeeds(self, rowGrowth=0, rowGen=0): """ Computes weed biomass, LAI, and litterfall. Allows setting the maximum green mass (stand foliage + weeds) \n limit; if exceeded, weed mass is reduced. Weeding is given as time list (time in months); in weeding \n all weed biomass is located to litter from dead plants (no retranslocation). Litter produced \n because of weed longevity is located to litter from living plants (retranslocation allowed): \n Kwargs: \n - rowGrowth - row index in parameter file growth and yield page \n - rowGen - row index in parameter file general parameters \n Input: \n - maxGreenMass - maximum allowed sum of tree stand foliage and weed above ground mass, kg ha-1 \n - weedings - time of weeding, as list, month at when weeding is applied \n - appAge - apparent age in yrs, np.array (adjusted age because of Type 1 growth response) \n - BiFoliage - mass of tree stand foliage, kg ha-1 \n Output: \n - weedAbove - above ground weed biomass, kg ha-1 - weedBelow - below ground weed biomass, kg ha-1 - weedALitL - weed litter from living above ground plant parts, allows retranslocation, kg ha-1 timestep-1 - weedALitD - weed litter from dead above ground plant parts, no retranslocation, kg ha-1 timestep-1 - weedBLitL - weed litter from living below ground plant parts, allows retranslocation, kg ha-1 timestep-1 - weedBLitD - weed litter from dead below ground plant parts, no retranslocation, kg ha-1 timestep-1 - weedLAI - weed leaf area index, m2 m-2 """ print(' + Weeds') #-----These to arguments-------------- if len(self.weedings) == 0: self.weedings = np.array( [0, 3, 6, 9, 18]) #give the age when weeding is done, in months weedings = self.weedings age = self.age nsteps = int(len(age) / (self.rotation + 1)) appAge = self.appAge[-nsteps:] BiFoliage = self.BiFoliage[-nsteps:] weedALitD = np.zeros(nsteps) weedBLitD = np.zeros(nsteps) #--------Parameters--------------- r = rowGrowth rg = rowGen maxGreenMass = self.g['maxGreenMass'][ rg] #maximum green mass includes above ground weed mass and tree stand foliage mass self.maxGreenMass = maxGreenMass b0 = self.p['Wbeta0'][r] b1 = self.p['Wbeta1'][ r] #Weed biomass growth parameters, result in kg/ha sa = self.g['WeedLeafA'][rg] rsr = self.g['WeedRSRatio'][ rg] #Specific leaf area weeds kg/m2, root to shoot-ratio wl = self.g['WeedsLon'][rg] #weed logevity dt = self.g['dt'][0] / 12.0 Nrotat = int(self.g['Nrotat'][0]) a_age = np.arange(dt, dt * nsteps + dt, dt) #age between 0...end of rotation yrs fW = lambda a: b0 * np.exp(-b1 / a) #----Construction of weed age series, in weeding set to 0. Apparent age is used in the growth computation, allows consuderation of fertilization, a1 = np.diff(appAge) a1 = np.insert(a1, 0, a1[0]) n = 0 aa = 0 tmpAges = [] k = 0 for a in range(nsteps): if n < len(weedings): if a == weedings[n]: aa = 0 n += 1 else: aa += a1[k] tmpAges.append(aa) else: aa += a1[k] tmpAges.append(aa) tmpAges = np.array(tmpAges) + 0.001 #to avoid zero division #---weed biomass computation ---------------------- WLAI = fW(tmpAges) * sa / 10000.0 # Leaf area index m2/m2 weedAbove = fW(tmpAges) # Above ground weed mass kg/ha weedBelow = weedAbove * rsr # Below ground weed mass kg/ha potTot = weedAbove + BiFoliage # potential total green biomass in site cutTot = np.minimum( potTot, maxGreenMass) # cut the fraction exceeding the maximum green mass cutWeed = np.where( cutTot - potTot < 0, cutTot - potTot, 0.0) # the exceeding part is subtracted from weed mass weedAbove = np.maximum(weedAbove + cutWeed, 0.0) # adjust the weed mass weedBelow = weedAbove * rsr t = interS(a_age, weedAbove, k=1) #t = interS(tmpAges, weedAbove, k=1) dW = t.derivative() #-------Litter production by age and longevity (retranslocation allowed) --------------- weedALitL = self.computeWeedLitter( age, nsteps, wl, dt, weedAbove) # above ground litter production, kg ha-1 timestep-1 weedBLitL = weedALitL * rsr # below ground litter production, kg ha-a timestep-1 weedDecline = np.where(dW(a_age) < 0, dW(a_age), 0.0) * dt * -1.0 weedALitL = weedALitL + weedDecline weedBLitL = weedBLitL + weedDecline * rsr #-------In weeding, locate weed biomass to litter from dead plants (no retranslocation) n = 0 for a in range(nsteps): if a == weedings[n]: aa = tmpAges[a - 1] + dt if a != 0 else 0.001 weedALitL[a] = 0.0 weedBLitL[a] = 0.0 weedALitD[a] = fW(aa) weedBLitD[a] = weedALitD[a] * rsr n += 1 if n == len(weedings): break #-------Initialize litter with final biomass if self.rotation == 0: weedALitD[0] = weedAbove[-1] weedBLitD[0] = weedBelow[-1] if self.rotation < Nrotat - 1: weedALitD[nsteps - 1] = weedAbove[-1] weedBLitD[nsteps - 1] = weedBelow[-1] #-------append results into instance variables -------------- self.weedAbove = np.append(self.weedAbove, weedAbove) self.weedBelow = np.append(self.weedBelow, weedBelow) self.weedALitL = np.append(self.weedALitL, weedALitL) self.weedBLitL = np.append(self.weedBLitL, weedBLitL) self.weedALitD = np.append(self.weedALitD, weedALitD) self.weedBLitD = np.append(self.weedBLitD, weedBLitD) self.weedLAI = np.append(self.weedLAI, WLAI)
def fLitter(self, rowGen=0): """ Computes litterfall from longevity (life span, years) and quantity of biomass components. Makes distinction between litter generated from living trees and that from dead stems. Composes continous function from acculumation of biomass fractions, computes its derivative, and uses it with biomass fraction life span to compute the litter output. \n Output: \n FoLitL - foliage litter from living trees (kg/ha/time step) \n BaLitL - bark litter from living trees (kg/ha/timestep) \n BrLitL - branch litter from living trees (kg/ha/timestep) \n FineRLitL - fine root litter from living trees (kg/ha/timestep) \n \n FoLitD - foliage litter from dead trees (kg/ha/timestep) \n BaLitD - bark litter from dead trees (kg/ha/timestep) \n BrLitD - branch litter from dead trees (kg/ha/timestep) \n RootLitD - root litter from dead trees (kg/ha/timestep) \n CWD - stems (coarse woody debris, kg/ha/timestep) Kwargs: \n rowGen - row index in parameterfile General parameters """ #-----Parameters------------------------- print(' + Litter') rg = rowGen BarkLon = self.g['BarkLon'][rg] BranchLon = self.g['BranchLon'][rg] LeafLon = self.g['LeafLon'][rg] FineRLon = self.g['FineRLon'][rg] FineRootPr = self.g['FineRootPr'][rg] nsteps = int(len(self.age) / (self.rotation + 1)) dt = self.g['dt'][0] / 12.0 Nrotat = int(self.g['Nrotat'][0]) age = self.age[-nsteps:] Survival = self.Survival[-nsteps:] BiFoliage = self.BiFoliage[-nsteps:] BiBark = self.BiBark[-nsteps:] BiBranch = self.BiBranch[-nsteps:] RootMass = self.RootMass[-nsteps:] MerchMass = self.MerchMass[-nsteps:] #-----compute litter production from living trees FoGrTree = self.FoGrTree[-nsteps:] BaGrTree = self.BaGrTree[-nsteps:] BrGrTree = self.BrGrTree[-nsteps:] RootGrTree = self.RootGrTree[-nsteps:] FineRootGrTree = self.FineRootGrTree[-nsteps:] FoLitL = shift(FoGrTree, int(LeafLon / dt), cval=0.0) * Survival # litterfall kg/ha/timestep BaLitL = shift(BaGrTree, int(BarkLon / dt), cval=0.0) * Survival BrLitL = shift(BrGrTree, int(BranchLon / dt), cval=0.0) * Survival FineRLitL = shift(FineRootGrTree, int(FineRLon / dt), cval=0.0) * Survival #-------compute litter production caused by mortality fsur = interS(age, Survival, k=1) dS = fsur.derivative() #rate of mortailty M = dS(age) * dt * -1.0 #Number of dead trees in time step FoLitD = BiFoliage * M / Survival BaLitD = BiBark * M / Survival BrLitD = BiBranch * M / Survival RootLitD = RootMass * M / Survival CWD = MerchMass * M / Survival #-------add logging residues to litter production, initialization and then at the end of rotation ------------ optLResid = True #in Ireland, no previous stand if optLResid: if self.rotation == 0: FoLitD[0] = BiFoliage[-1] BaLitD[0] = BiBark[-1] * 0.3 BrLitD[0] = BiBranch[-1] RootLitD[0] = RootMass[-1] * 0.5 #stumps & roots CWD[0] = sum(CWD) * 0.5 + RootMass[-1] * 0.5 if self.rotation < Nrotat - 1: FoLitD[nsteps - 1] = BiFoliage[-1] BaLitD[nsteps - 1] = BiBark[-1] * 0.3 BrLitD[nsteps - 1] = BiBranch[-1] RootLitD[nsteps - 1] = RootMass[-1] * 0.5 CWD[nsteps - 1] = RootMass[-1] * 0.5 #-------append results into instance variables -------------- self.FoLitL = np.append(self.FoLitL, FoLitL) self.BaLitL = np.append(self.BaLitL, BaLitL) self.BrLitL = np.append(self.BrLitL, BrLitL) self.FineRLitL = np.append(self.FineRLitL, FineRLitL) self.FoLitD = np.append(self.FoLitD, FoLitD) self.BaLitD = np.append(self.BaLitD, BaLitD) self.BrLitD = np.append(self.BrLitD, BrLitD) self.RootLitD = np.append(self.RootLitD, RootLitD) self.CWD = np.append(self.CWD, CWD) del FoLitL, FoLitD, BaLitL, BaLitD, BrLitL, BrLitD, FineRLitL, RootLitD, CWD
def releaseFert(self, gy): """ fertilizer parameters: N (%), P2O5 (%), K2O (%), release kinetics parameters (month^-1): Kn, Kp, Kk """ nStems = gy.Survival[0] # number of stems /ha dt = gy.g['dt'][0] Nrotat = int( gy.g['Nrotat'][0]) # time step (months), number of rotations lr = gy.g['Lrotat'][0] * 12.0 dtInRot = lr / dt # length of rotation (months), timesteps in rotation length = int(Nrotat * dtInRot * dt + Nrotat) # lenght of array including all timesteps self.ferSto = np.zeros(length) self.ferRel = np.zeros( length ) # undissonlved fertilizer in soil kg ha-1, nutrient release kg ha-1 timestep-1 ferSto = np.zeros(length) # local: fertilizer nutrient storage ferRel = np.zeros(length) time = np.arange(0, length) if self.name == 'Nitrogen': name = 'N' k = 'Kn' conv = 1.0 if self.name == 'Phosphorus': name = 'P' k = 'Kp' conv = 0.42 if self.name == 'Potassium': name = 'K' k = 'Kk' conv = 0.83 fpara = { 'NPK': { 'N': 23, 'P': 13, 'K': 7, 'Kn': 0.94, 'Kp': 0.04, 'Kk': 0.3 }, 'Suburin': { 'N': 7, 'P': 15, 'K': 8, 'Kn': 0.94, 'Kp': 0.04, 'Kk': 0.3 }, 'Urea': { 'N': 45, 'P': 0, 'K': 0, 'Kn': 0.9, 'Kp': 0, 'Kk': 0 }, 'RP': { 'N': 0, 'P': 9, 'K': 0, 'Kn': 0, 'Kp': 0.04, 'Kk': 0 }, 'Kcl': { 'N': 0, 'P': 0, 'K': 60, 'Kn': 0, 'Kp': 0, 'Kk': 0.8 }, 'Ash': { 'N': 0, 'P': 2.8 / 0.42, 'K': 7.6 / 0.83, 'Kn': 0, 'Kp': 0.01, 'Kk': 0.01 }, } for r in range(Nrotat): # rotation for t in range(len(self.fer['time'])): # times in input data frame m = int(self.fer['time'][t] + (r * lr)) # fertilization month for a in self.fer.keys(): # fertilizers in use if self.fer[a][t] != 0 and a != 'time': # dose is > 0 feNut = fpara[a][name] kk = fpara[a][k] dose = self.fer[a][t] if kk > 0: # nutrient is in the fertilizer #print feNut, self.name, conv, nStems, kk feNut = feNut * conv / 100. * dose * nStems / 1000.0 ts = time[m:] - m ferSto[m:] = ferSto[m:] + feNut * np.exp( -kk * ts * dt) ferRel[m:] = ferRel[m:] + feNut * ( 1 - np.exp(-kk * ts * dt)) func = interS(time, ferRel, k=1) dfdt = func.derivative() self.ferRel = dfdt(time) * dt self.ferSto = ferSto
def CWTr(nLyrs, z, dz, pF, Ksat, direction='positive'): """ Returns interpolation functions sto=f(gwl) profile water storage as a function ofground water level gwl=f(sto) ground water level tra=f(gwl) transmissivity Input: nLyrs number of soil layers d depth of layer midpoint dz layer thickness pF van Genuchten water retention parameters: ThetaS, ThetaR, alfa, n Ksat saturated hydraulic conductivity in m s-1. K in m/day. direction: positive or negative downwards """ #-------Parameters --------------------- z = np.array(z) dz = np.array(dz) #--------- Connection between gwl and water storage------------ if direction == 'positive': print('no positive direction available') import sys sys.exit() gwl = np.linspace(0., sum(dz), 500) sto = [sum(wrc(pF, x=np.minimum(z - g, 0.0)) * dz) for g in gwl] #equilibrium head m else: # Previously... gwl = np.linspace(0., -sum(dz), 100) sto = [sum(wrc(pF, x=np.minimum(z + g, 0.0)) * dz) for g in gwl] #equilibrium head m gwlabove = [2.0, 1.5, 0.5] stoabove = [ sto[0] + 0.75 * gwlabove[0], sto[0] + 0.3 * gwlabove[1], sto[0] + 0.2 * gwlabove[2] ] stoT = list(stoabove) + list(sto) gwlT = list(gwlabove) + list(gwl) gwlToSto = interp1d(np.array(gwlT), np.array(stoT), fill_value='extrapolate') stoT = list(stoT) gwl = list(gwlT) sto.reverse() gwl.reverse() stoToGwl = interp1d(np.array(stoT), np.array(gwlT), fill_value='extrapolate') cc = np.gradient(gwlToSto(gwlT)) / np.gradient(gwlT) # cc = np.gradient(gwlToSto(gwlT), gwlT) # Iñaki cc[cc < 0.2] = 0.2 C = interp1d(np.array(gwlT), cc, bounds_error=False, fill_value=(0., 1.)) #storage coefficient function # New approach, 28.V: to test Boussinesq eq. with theta. # Idea of the new approach. C is not smooth enough with current parameters, so we take the liberty to return # theta as if it was the C = d \theta/ dh, because theta is smooth. This leads to the integral of theta # working as theta itself, because integral(C dh) = \theta by def of C. # All this is to test solving the Boussinesq eq in both the phi and the theta form and compare them. # theta is the volumetric water content # theta_integral is integral of theta over dh zeta = -z theta = interp1d(zeta, wrc(pF, zeta), bounds_error=False, fill_value=(wrc(pF[-1], zeta[-1]), 1.)) # C = interp1d(zeta, -np.gradient(theta(zeta), dz[0])) theta_integral = interp1d(zeta[::-1], np.cumsum(theta(zeta)[::-1]), bounds_error=False) # This is the line where the labyrinth happens C = theta theta = theta_integral #import matplotlib.pylab as plt #plt.plot(gwlT, cc, 'ro') #plt.plot(gwlT, C(gwlT), 'b-') #import sys; sys.exit() gwlToSto = interp1d(np.array(gwlT), np.array(stoT), fill_value='extrapolate') stoT = list(stoT) gwlT = list(gwlT) stoT.reverse() gwlT.reverse() stoToGwl = interp1d(np.array(stoT), np.array(gwlT), fill_value='extrapolate') del gwlT, stoT #----------Transmissivity------------------- K = np.array(Ksat * 86400.) #from m/s to m/day tr = [sum(K[t:] * dz[t:]) for t in range(nLyrs)] if direction == 'positive': gwlToTra = interS(z, np.array(tr)) else: z = list(z) z.reverse() tr.reverse() gwlToTra = interS( -np.array(z), np.array(tr), k=3, ext='const') # returns limiting value outside interpolation domain del tr return gwlToSto, K, gwlToTra, C, theta
def composeDwtFromTs(self, time): from scipy.interpolate import InterpolatedUnivariateSpline as interS t = np.array(self.Time['time']) dwt = np.array(self.Time['wt']) * -1 smooth = interS(t, dwt, k=1) return smooth(time)