180 likes | 327 Vues
Modelling above- and below-ground competition together Winfried Kurth University of Göttingen Chair for Computer Graphics and Ecological Informatics wk<at>informatik.uni-goettingen.de. Summer School „Modelling and Simulation with GroIMP“, Göttingen, 27 Sept.- 1st Oct. 2010. Problem:
E N D
Modelling above- and below-ground competition together Winfried Kurth University of Göttingen Chair for Computer Graphics and Ecological Informatics wk<at>informatik.uni-goettingen.de Summer School „Modelling and Simulation with GroIMP“, Göttingen, 27 Sept.- 1st Oct. 2010
Problem: observation from ecology (A. Polle, oral communication): under optimal conditions (plenty of resources): mono-species stand gives higher yield than mixed stand under stress (lack of resources): mixed stand outperforms mono-species stand mono-species biomass multi-species stress How to model this in a simple way in GroIMP ?
assume 2 species, competing at two levels (e.g., above-/below-ground) • at 1st level: allelopathic interaction • i.e. species A inhibits growth of species B in its neighbourhood, • competition between A and B is stronger than between A and A or • between B and B • at 2nd level: complementarity between species A and B, • e.g. both compete for the same soil resource but at different depths • intraspecific competition is stronger than that between A and B • Now if we decrease the availability of the soil resource (i.e., induce stress), the competition at 2nd level will gradually dominate that at 1st level; hence the advantage of the monospecies composition will vanish.
Objects of the model: module Stand; module Seedling(int species); module Tree(float size, int age, int species) /* size = radius */ { double nutr = satNut; } ==> if (species == 1) ( c:Cone.(setLength(3*size), setRadius(size)) {{c.setColor(0x007700);}} ) else ( Translate(0, 0, 2*size) Scale(1.0, 1.0, 2.0) s:Sphere.(setRadius(size)) {{s.setColor(0x22ee00);}} ); module Soil; module SoilPatch(int k) extends Box(3.0, 3.0, 3.0).(setColor(0xffaa88)) { float nutrAvail = startNutr; float nutrDemand, nutrTransf; int nbPlants; }
Competition at level 1 (allelopathy): boolean notOutcompeted(Tree a) { return empty( (* t:Tree, ((t!=a) && (( t[species] == a[species] && distance(a, t) <= inhib1 * t[size] ) || ( t[species] != a[species] && distance(a, t) <= inhib2 * t[size] )) && (t[size] >= a[size]) ) *) ); } t a const double inhib1 = 1.; /* inhibition zone: competitors of same species */ const double inhib2 = 2.; /* inhibition zone: competitors of other species */
Competition at level 2 (complementarity): each plant takes nutrients from all soil patches within its depletion radius species 1 from layer 1, species 2 from layer 2
Renewal of nutrients in a soil patch (from influx, mineralization etc.): protected void updateNutr() [ p:SoilPatch ::> { p[nutrAvail] += renewRate; p[nutrAvail] = Math.min(p[nutrAvail], patchCapacity); p[nbPlants] = 0; p[nutrTransf] = 0.; p[nutrDemand] = 0.; colorize(p); } ]
Checking the total demand exerted on a single soil patch: protected void checkDemand() [ t:Tree, p:SoilPatch, (inDepletionZone(p, t)) ::> { p[nutrDemand] += maxConsumption; p[nbPlants]++; } ] number of plants taking nutrients from this soil patch p
How to check if p is in the depletion zone of t : boolean inDepletionZone(SoilPatch p, Tree a) { return (p[k] == a[species] && projDistance(p, a) <= depletionRadius); } double projDistance(SoilPatch p, Tree a) /* distance in xy plane */ { double dx = p[Location.X] - a[Location.X]; double dy = p[Location.Y] - a[Location.Y]; return Math.sqrt(dx*dx + dy*dy); }
The amount of nutrient which one plant can really obtain from this soil patch is calculated and written to the soil patch attribute nutrTransf : protected void writeDemand() [ p:SoilPatch ::> { if (p[nbPlants] > 0) p[nutrTransf] = Math.min(p[nutrAvail], p[nutrDemand]) / (double) p[nbPlants]; } ]
Nutrient transfer from soil to plants takes place: protected void transferNutr() [ t:Tree, p:SoilPatch, (inDepletionZone(p, t)) ::> { t[nutr] += p[nutrTransf]; p[nutrAvail] -= p[nutrTransf]; } ]
Growth of a plant depends on its size, age, and accumulated nutrients during the last time step. All nutrients are consumed during the growth step: protected void grow() [ a:Tree(size, age, k) ==> if (notOutcompeted(a) && a[nutr] >= minNeed) ( t:Tree(growthf(size, age, a[nutr]), age+1, k) { t[nutr] = 0.; } ); ] public float growthf(double x, int age, double nutr) { double incr; if (nutr < minNeed) incr = 0.; else { if (nutr < satNut) incr = ((nutr-minNeed)/(satNut-minNeed))*maxIncr; else incr = maxIncr; } if (age < max_age) return (float) (x+incr); else return (float) x; } incr maxIncr nutr minNeed satNut
Initialization of the whole scene: protected void init() { step = 1; masstable.clear().setColumnKey(0, "biomass"); chart(masstable, XY_PLOT); int nbPx = (int) Math.floor((x_extens + depletionRadius) / patchDist); int nbPy = (int) Math.floor((y_extens + depletionRadius) / patchDist); for ( apply(3) ) /* 3 steps of rule application are carried out: */ [ Axiom ==> [ Translate(x_extens/2, y_extens/2, -1) /* soil surface: */ Box(0.2, x_extens, y_extens).(setColor(0xffffcc)) ] StandSoil; Stand ==> for ((1:n1)) ( [ Translate(random(0, x_extens), random(0, y_extens), 0) Seedling(1) ] ) for ((1:n2)) ( [ Translate(random(0, x_extens), random(0, y_extens), 0) Seedling(2) ] ); Soil ==> for (int i: (1:nbPx)) ( for (int j: (1:nbPy)) ( for (int k: (1:2)) /* two layers of patches */ ([ Translate(-0.5*depletionRadius + i*patchDist, -0.5*depletionRadius + j*patchDist, -k*layerDist) p:SoilPatch(k) { colorize(p); } ]) ) ); s:Seedling(k) ==> if (s[Location.X] >= 0 && s[Location.Y] >= 0 && s[Location.X] <= x_extens && s[Location.Y] <= y_extens) ( Tree(random(1.0, 5.0), 1, k) ); /* size = random, age = 1, species = k */ ] }
Colorizing the patches for visualization of nutrient content: void colorize(SoilPatch p) { double a = p[nutrAvail]; if (a >= 0.875*patchCapacity) p.setColor(0xff4444); else if (a >= 0.75*patchCapacity) p.setColor(0xff7744); else if (a >= 0.625*patchCapacity) p.setColor(0xffaa44); else if (a >= 0.5*patchCapacity) p.setColor(0xffdd44); else if (a >= 0.375*patchCapacity) p.setColor(0xffff55); else if (a >= 0.25*patchCapacity) p.setColor(0xffff88); else if (a >= 0.125*patchCapacity) p.setColor(0xffffbb); else p.setColor(0xffffff); } (red yellow white)
Main rule block which controls the order of rule applications and adds a biomass entry after each growth step for a chart: public void run() { updateNutr(); checkDemand(); writeDemand(); transferNutr(); grow(); masstable.addRow().set(0, sum( biomass( (* t:Tree *) ))); println(step); step++; } public float biomass(Tree a) /* assumed as proportional to basal area */ { return (float) (Math.PI * a[size] * a[size]); }
for completeness: parameters of the model and variable declarations const int n1 = 200; /* nb. of trees of species 1 */ const int n2 = 200; /* nb. of trees of species 2 */ const int max_age = 40; /* number of growth steps */ const double x_extens = 500.; const double y_extens = 300.; const double inhib1 = 1.; /* inhibition zone: competitors of same species */ const double inhib2 = 2.; /* inhibition zone: competitors of other species */ const double renewRate = 20.0; /* let this vary! */ const double patchCapacity = 50.0; const double startNutr = 50.0; const double maxConsumption = 0.4; /* max. consumption of a tree from a single soil patch; 0.4 */ const double patchDist = 15.; const double depletionRadius = 40.; const double layerDist = 5.; const double minNeed = 4.; /* necessary min. amount of nutrients for survival */ const double satNut = 10.; /* saturation point of nutrient response function */ const double maxIncr = 0.7; /* max. tree growth rate */ int step; /* step counter */ const DatasetRef masstable = new DatasetRef("stand biomass"); /* table for chart */ example file div_compet2b.rgg
Resulting stand biomass under different parameterizations (unstressed = plenty of nutrients by high renewal rate in the soil; stressed = competition for nutrients is severely limiting)