class GrossGrowthEfficiency: """ to N: beta*(1-epsilon) to D: 1-beta to Z: beta*epsilon """ assimilated_consumer = phydra.variable(foreign=True, flux='assimilation') egested_detritus = phydra.variable(foreign=True, flux='egestion') excreted_nutrient = phydra.variable(foreign=True, flux='excretion') beta = phydra.parameter(description='absorption efficiency') epsilon = phydra.parameter(description='net production efficiency') @phydra.flux(group_to_arg='graze_out') def assimilation(self, assimilated_consumer, egested_detritus, excreted_nutrient, graze_out, beta, epsilon): return self.m.sum(graze_out) * beta * epsilon @phydra.flux(group_to_arg='graze_out') def egestion(self, assimilated_consumer, egested_detritus, excreted_nutrient, graze_out, beta, epsilon): return self.m.sum(graze_out) * (1 - beta) @phydra.flux(group_to_arg='graze_out') def excretion(self, assimilated_consumer, egested_detritus, excreted_nutrient, graze_out, beta, epsilon): return self.m.sum(graze_out) * beta * (1 - epsilon)
class HollingTypeIII: resource = phydra.variable(foreign=True, flux='grazing', negative=True) consumer = phydra.variable(foreign=True, flux='grazing', negative=False) feed_pref = phydra.parameter(description='feeding preferences') Imax = phydra.parameter(description='maximum ingestion rate') kZ = phydra.parameter(description='feeding preferences') @phydra.flux def grazing(self, resource, consumer, feed_pref, Imax, kZ): return Imax * resource ** 2 \ * feed_pref / (kZ ** 2 + self.m.sum([resource ** 2 * feed_pref])) * consumer
class MonodGrowth_mu_ConsumerDim: resource = phydra.variable(foreign=True, flux='uptake', negative=True) consumer = phydra.variable(foreign=True, dims='var', flux='uptake', negative=False) # dims='var', halfsat = phydra.parameter( dims='var', description='half-saturation constant') # dims='var' mu_max = phydra.parameter(dims='var', description='maximum growth rate') @phydra.flux(dims='var') def uptake(self, resource, consumer, halfsat, mu_max): return mu_max * resource / (resource + halfsat) * consumer
class EMPOWER_Monod_ML: """ """ resource = phydra.variable(foreign=True) halfsat = phydra.parameter(description='monod half-saturation constant') @phydra.flux(group='growth_lims') def monod_lim(self, resource, halfsat): return resource / (resource + halfsat)
class SizebasedGrazingKernel_Dims: """ ASTroCAT Grazing Kernel """ resource = phydra.variable(foreign=True, dims='resource') consumer = phydra.variable(foreign=True, dims='consumer') phiP = phydra.parameter(dims=('resource', 'consumer'), description='feeding preferences') Imax = phydra.parameter(dims='consumer', description='maximum ingestion rate') KsZ = phydra.parameter(description='half sat of grazing') @phydra.flux(group='graze_matrix', dims=('resource', 'consumer')) def grazing(self, resource, consumer, phiP, Imax, KsZ): """ """ PscaledAsFood = phiP / KsZ * resource[:, None] FgrazP = Imax * consumer * PscaledAsFood / ( 1 + self.m.sum(PscaledAsFood, axis=0)) return FgrazP
class GlobalSlabClimatologyForcing: forcing = phydra.forcing(foreign=False, setup_func='forcing_setup') dataset = phydra.parameter(description="Options: 'n0x', 'mld', 'tmld', 'par'") lat = phydra.parameter(description='constant value of forcing') lon = phydra.parameter(description='constant value of forcing') rbb = phydra.parameter(description='constant value of forcing') smooth = phydra.parameter(description='smoothing conditions, larger values = stronger smoothing') k = phydra.parameter(description='The degree of the spline fit') deriv = phydra.parameter(description='order of derivative to store, for basic forcing pass 0') def forcing_setup(self, dataset, lat, lon, rbb, smooth, k, deriv): data = ClimatologyForcing(lat, lon, rbb, dataset).outForcing dayspermonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] dpm = dayspermonth dpm_cumsum = np.cumsum(dpm) - np.array(dpm) / 2 time = np.concatenate([[0], dpm_cumsum, [365]], axis=None) boundary_int = [(data[0] + data[-1]) / 2] dat = np.concatenate([boundary_int, data, boundary_int], axis=None) spl = intrp.splrep(time, dat, per=True, k=k, s=smooth) def forcing(time): return intrp.splev(np.mod(time, 365), spl, der=deriv) return forcing
class SlabSinking: """ """ var = phydra.variable(foreign=True, flux='sinking', negative=True) mld = phydra.forcing(foreign=True) rate = phydra.parameter(description='sinking rate, units: m d^-1') @phydra.flux def sinking(self, var, rate, mld): return var * rate / mld
class Eppley_ML: """ """ temp = phydra.forcing(foreign=True, description='Temperature forcing') eppley_exp = phydra.parameter(description='eppley exponent') @phydra.flux(group='growth_lims') def eppley_growth(self, temp, eppley_exp): return self.m.exp(eppley_exp * temp)
class EMPOWER_Eppley_ML: """ """ temp = phydra.forcing(foreign=True, description='Temperature forcing') VpMax = phydra.parameter( description='Maximum photosynthetic rate at 0 degrees celcius') @phydra.flux(group='VpT') def temp_dependence(self, temp, VpMax): return VpMax * 1.066**temp
class HollingTypeIII_ResourcesListInput_Consumption2Group: """ """ resources = phydra.variable(foreign=True, negative=True, flux='grazing', list_input=True, dims='resources') consumer = phydra.variable(foreign=True) feed_prefs = phydra.parameter( dims='resources', description='feeding preference for resources') Imax = phydra.parameter(description='maximum ingestion rate') kZ = phydra.parameter(description='feeding preferences') @phydra.flux(group='graze_out', dims='resources') def grazing(self, resources, consumer, feed_prefs, Imax, kZ): scaled_resources = resources**2 * feed_prefs return scaled_resources * Imax / ( kZ**2 + self.m.sum(scaled_resources)) * consumer
class Steele_ML: """ """ pigment_biomass = phydra.variable(foreign=True) i_0 = phydra.forcing(foreign=True, description='Light forcing') mld = phydra.forcing(foreign=True, description='Mixed Layer Depth forcing') i_opt = phydra.parameter(description='Optimal irradiance of consumer') kw = phydra.parameter(description='light attenuation coef for water') kc = phydra.parameter( description='light attenuation coef for pigment biomass') @phydra.flux(group='growth_lims') def steele_light_lim(self, i_0, mld, pigment_biomass, i_opt, kw, kc): kPAR = kw + kc * pigment_biomass light_lim = 1. / (kPAR * mld) * (-self.m.exp(1. - i_0 / i_opt) - (-self.m.exp( (1. - (i_0 * self.m.exp(-kPAR * mld)) / i_opt)))) return light_lim
class Mixing_K: """ pre-computes K to be used in all mixing processes """ mld = phydra.forcing(foreign=True) mld_deriv = phydra.forcing(foreign=True) kappa = phydra.parameter(description='constant mixing coefficient') @phydra.flux(group='mixing_K') def mixing(self, mld, mld_deriv, kappa): return (self.m.max(mld_deriv, 0) + kappa) / mld
class Monod_ML_ConsumerDim: """ """ resource = phydra.variable(foreign=True) halfsat = phydra.parameter(dims='vars', description='monod half-saturation constant') @phydra.flux(dims='vars', group='growth_lims') def monod_lim(self, resource, halfsat): #print("in monod lim", resource, halfsat) #print(resource.value.ndim) return resource / (resource + halfsat)
class MonodGrowth: resource = phydra.variable(foreign=True, flux='uptake', negative=True) consumer = phydra.variable(foreign=True, flux='uptake', negative=False) # dims='var', halfsat = phydra.parameter( description='half-saturation constant') # dims='var' @phydra.flux def uptake(self, resource, consumer, halfsat): return resource / (resource + halfsat) * consumer
class LinearExchange_SourceDim: """ """ source = phydra.variable(foreign=True, dims='var', flux='decay', negative=True) sink = phydra.variable(foreign=True, flux='decay', negative=False) rate = phydra.parameter(description='decay/mortality rate') @phydra.flux(dims='var') def decay(self, source, sink, rate): return source * rate
class SubFlux: var = phydra.variable(foreign=True) rate = phydra.parameter() @phydra.flux(group='X_growth') def one_growth(self, var, rate): # print(var) return var * rate @phydra.flux(group='X_growth') def two_growth(self, var, rate): return - var * rate
class EMPOWER_Growth_ML: """ XXX """ resource = phydra.variable(foreign=True, flux='growth', negative=True) consumer = phydra.variable(foreign=True, flux='growth', negative=False) mu_max = phydra.parameter(description='maximum growth rate') @phydra.flux(group_to_arg='growth_lims') def growth(self, resource, consumer, mu_max, growth_lims): # print("in growth flux func now", resource, consumer, mu_max, growth_lims) return mu_max * self.m.product(growth_lims) * consumer
class Growth_Monod_Eppley_Steele: resource = phydra.variable(foreign=True, flux='growth', negative=True) consumer = phydra.variable(foreign=True, flux='growth', negative=False) Temp = phydra.forcing(foreign=True, description='Temperature forcing') Light = phydra.forcing(foreign=True, description='Light forcing') MLD = phydra.forcing(foreign=True, description='Mixed Layer Depth forcing') halfsat = phydra.parameter(description='monod half-saturation constant') eppley = phydra.parameter(description='eppley exponent') i_opt = phydra.parameter(description='Optimal irradiance of consumer') µ_max = phydra.parameter(description='maximum growth rate') kw = phydra.parameter(description='light attenuation coef for water') kc = phydra.parameter(description='light attenuation coef for consumer') @phydra.flux def growth(self, resource, consumer, Temp, Light, MLD, i_opt, kw, kc, eppley, halfsat, µ_max): temp_lim = self.m.exp(eppley * Temp) monod_lim = resource / (resource + halfsat) kPAR = kw + kc * consumer light_lim = 1. / (kPAR * MLD) * ( -self.m.exp(1. - Light / i_opt) - ( -self.m.exp((1. - (Light * self.m.exp(-kPAR * MLD)) / i_opt)))) return µ_max * temp_lim * monod_lim * light_lim * consumer
class LinearForcingInput: var = phydra.variable(foreign=True, flux='input', negative=False, description='variable affected by flux') forcing = phydra.forcing(foreign=True, description='forcing affecting flux') rate = phydra.parameter(description='linear rate of change') @phydra.flux def input(self, var, forcing, rate): """ """ return forcing * rate
class ExponentialGrowth_Dim: """ """ var = phydra.variable(foreign=True, dims='var', flux='growth', negative=False, description='variable affected by flux') rate = phydra.parameter(description='linear rate of change') @phydra.flux(dims='var') def growth(self, var, rate): """ """ return var * rate
class QuadraticExchange_SourceDim: """ """ source = phydra.variable(foreign=True, dims='var', flux='decay', negative=True) sink = phydra.variable(foreign=True, flux='decay', negative=False) rate = phydra.parameter(description='quadratic rate of change') @phydra.flux(dims='var') def decay(self, source, sink, rate): """ """ return source**2 * rate
class QuadraticDecay_Dim: """ """ var = phydra.variable(foreign=True, dims='var', flux='decay', negative=True, description='variable affected by flux') rate = phydra.parameter(description='quadratic rate of change') @phydra.flux(dims='var') def decay(self, var, rate): """ """ return var**2 * rate
class LinearDecay_VarDim: """ """ var = phydra.variable(dims='var', foreign=True, flux='decay', negative=True, description='variable affected by flux') rate = phydra.parameter(description='linear rate of decay/mortality') @phydra.flux(dims='var') def decay(self, var, rate): """ """ return var * rate
class LinearInput_Dim: """ """ var = phydra.variable(foreign=True, dims='var', flux='input', negative=False, description='variable affected by flux') rate = phydra.parameter(dims='var', description='linear rate of change') @phydra.flux(dims='var') def input(self, var, rate): """ """ return rate
class SinusoidalForcing: forcing = phydra.forcing(foreign=False, setup_func='forcing_setup') period = phydra.parameter(description='period of sinusoidal forcing') def forcing_setup(self, period): cwd = os.getcwd() print("forcing function is in directory:", cwd) @np.vectorize def forcing(time): return np.cos(time / period * 2 * np.pi) + 1 return forcing
class NoonPARfromLat: """ Component that calculates Photosynthetically Active Radiation (PAR) from Latitude""" NoonPAR = phydra.forcing(setup_func='calcNoonPAR', description='calculated PAR from Irradiance', attrs={'unit': 'W m^-2'}) station = phydra.parameter(description="name of station, options: 'india', 'biotrans', 'kerfix', 'papa'") def calcNoonPAR(self, station): """ Function adapted from EMPOWER model (Anderson et al. 2015)""" def noon_PAR_calc(jday, latradians, clouds, e0): albedo = 0.04 # albedo solarconst = 1368.0 # solar constant, w m-2 parrac = 0.43 # PAR fraction declin = 23.45 * np.sin(2 * np.pi * (284 + jday) * 0.00274) * np.pi / 180 # solar declination angle coszen = np.sin(latradians) * np.sin(declin) + np.cos(latradians) * np.cos(declin) # cosine of zenith angle zen = np.arccos(coszen) * 180 / np.pi # zenith angle, degrees Rvector = 1 / np.sqrt(1 + 0.033 * np.cos(2 * np.pi * jday * 0.00274)) # Earth's radius vector Iclear = solarconst * coszen ** 2 / (Rvector ** 2) / ( 1.2 * coszen + e0 * (1.0 + coszen) * 0.001 + 0.0455) # irradiance at ocean surface, clear sky cfac = (1 - 0.62 * clouds * 0.125 + 0.0019 * (90 - zen)) # cloud factor (atmospheric transmission) Inoon = Iclear * cfac * (1 - albedo) # noon irradiance: total solar noonparnow = parrac * Inoon return noonparnow if station == 'india': latitude = 60.0 # latitude, degrees clouds = 6.0 # cloud fraction, oktas e0 = 12.0 # atmospheric vapour pressure elif station == 'biotrans': latitude = 47.0 clouds = 6.0 e0 = 12.0 elif station == 'kerfix': latitude = -50.67 clouds = 6.0 e0 = 12.0 elif station == 'papa': latitude = 50.0 clouds = 6.0 e0 = 12.0 else: raise ValueError("station label not found, options: 'india', 'biotrans', 'kerfix', 'papa'") latradians = latitude * np.pi / 180. def return_noonPAR(time): return noon_PAR_calc(time, latradians, clouds, e0) return return_noonPAR
class GrossGrowthEfficiency_MatrixGrazing: """ to N: beta*(1-epsilon) to D: 1-beta to Z: beta*epsilon """ grazed_resource = phydra.variable(dims='resource', foreign=True, flux='grazing', negative=True) assimilated_consumer = phydra.variable(dims='consumer', foreign=True, flux='assimilation') egested_detritus = phydra.variable(foreign=True, flux='egestion') f_eg = phydra.parameter(description='fraction egested') epsilon = phydra.parameter(description='net production efficiency') @phydra.flux(dims='resource', group_to_arg='graze_matrix') def grazing(self, assimilated_consumer, egested_detritus, grazed_resource, graze_matrix, f_eg, epsilon): """ """ out = self.m.sum(graze_matrix, axis=1) return out @phydra.flux(dims='consumer', group_to_arg='graze_matrix') def assimilation(self, assimilated_consumer, egested_detritus, grazed_resource, graze_matrix, f_eg, epsilon): """ """ out = self.m.sum(graze_matrix, axis=0) * epsilon return out @phydra.flux(group_to_arg='graze_matrix') def egestion(self, assimilated_consumer, egested_detritus, grazed_resource, graze_matrix, f_eg, epsilon): """ """ out = self.m.sum(graze_matrix, axis=None) * (1 - f_eg - epsilon) return out
class EMPOWER_Smith_ML: """ """ pigment_biomass = phydra.variable(foreign=True) i_0 = phydra.forcing(foreign=True, description='Light forcing') mld = phydra.forcing(foreign=True, description='Mixed Layer Depth forcing') alpha = phydra.parameter(description='initial slop of PI curve') CtoChl = phydra.parameter(description='chlorophyll to carbon ratio') kw = phydra.parameter(description='light attenuation coef for water') kc = phydra.parameter( description='light attenuation coef for pigment biomass') @phydra.flux(group_to_arg='VpT', group='growth_lims') def smith_light_lim(self, i_0, mld, pigment_biomass, alpha, VpT, kw, kc, CtoChl): kPAR = kw + kc * pigment_biomass i_0 = i_0 / 24 # from per day to per h x_0 = alpha * i_0 # * self.m.exp(- kPAR * 0) # (== 1) x_H = alpha * i_0 * self.m.exp(-kPAR * mld) VpH = VpT / kPAR / mld * (self.m.log(x_0 + (VpT**2 + x_0**2)**0.5) - self.m.log(x_H + (VpT**2 + x_H**2)**0.5)) return VpH * 24 / CtoChl
class ConstantForcing: forcing = phydra.forcing(foreign=False, setup_func='forcing_setup') value = phydra.parameter(description='constant value of forcing') def forcing_setup(self, value): cwd = os.getcwd() print("forcing function is in directory:", cwd) print("forcing_val:", value) @np.vectorize def forcing(time): return value return forcing
class ListInputFlux: """ get variable input of multiple labels as list and do the routing etc. """ resources = phydra.variable(foreign=True, negative=True, flux='growth', list_input=True, dims='resources') consumer = phydra.variable(foreign=True, flux='growth') halfsats = phydra.parameter(dims='resources') @phydra.flux(dims='resources') def growth(self, resources, consumer, halfsats): print(resources, consumer, halfsats) print(sum(resources + halfsats)) out = resources / sum(resources + halfsats) * consumer print("out:", out, np.shape(out)) return out