Table of Contents

From light interception to photosynthesis

Should this be a class ?

Introducing light interception of leaves

Open the file Light.gsz. This is a simple model of a plant that will grow (produce new leaves and internodes) and branch, while each leaf intercepts light from a lamp hanging over it. One of the first things that come to mind when thinking about plant functions is, of course, photosynthesis. In order for the plant to do photosynthesis, it needs energy, water and CO2. Thus, first, some plant parts (normally the leaves), need to intercept and absorb light and transfer part of the energy gained from this process onto specialized molecules. Let us first look at light capture: There are several ways to model this. The nice thing about a 3D model is that we have information about the position of each leaf. We further have information about the position of light sources. To model the light environment in our 3D scene, we first have to create a light source. The following code defines a new lamp:

//a module defining the lamp:
module MyLamp extends SpotLight {
	{
		setPower(200.0); // power in W
		setAttenuationDistance(50.0); //in m
		setAttenuationExponent(0.0);
		setInnerAngle(22.5 * Math.PI/180.0);
		setOuterAngle(30 * Math.PI/180.0);
	}
}

This light source is a spotlight, we set a power of 200 W and two values for the opening angle of the lamp shader (inner and outer angle). The other parameters (concerning attenuation) are not interesting at the moment. Look up the class SpotLight in the API documentation:

This light source has to be coupled with a light node so that we can move it around in the scene. The following code defines a light node:

//a module defining the light node: 
module MyLight extends LightNode(0.0, 1.0, 1.0) {
	{ setLight(new MyLamp());}
}

Note that we actually also give the emitted light a color with the constructor of light node: The three arguments are for red, green, and blue, and at the moment this gives all white light. However, as the LightNode is “masked” by the object MyLamp you have to comment it out to see the effect of changing the color in the LightNode:

Before:

//a module defining the light node: 
module MyLight extends LightNode(0.0, 1.0, 1.0) {
	{ setLight(new MyLamp());}
}

After:

//a module defining the light node: 
module MyLight extends LightNode(0.0, 1.0, 1.0) {
	//{ setLight(new MyLamp());}
}

The lamp then has to be inserted into the scene, you do this inside the init() method:

protected void init()
[
	Axiom ==> //insert a plane here 
	[ Plane().(setShader(WHITE))]
 
	Bud(1, PHYLLOCHRON, 1);
	//insert the lamp here
	==>> ^ M(50) RU(180) MyLight;
]

Also, we want to insert a white plane into the scene so that the simulated plant is no longer “floating” in space but instead is rooted in the plane. The Plane object is inserted using its constructor, Plane(), followed by the setShader command, and it is put into brackets (like a branch) so that the turtle will be in the right position at the start of the simulation of the plant. We do this by precaution here – it is actually not really necessary (try removing the square brackets and see that the effect is the same). You might have noticed with astonishment that we now have a new type of transformation operator: \=⇒> It is called a graph transformation operator, and that’s all you need to know for now. In fact, we use it to insert things like lamps that do not strictly belong to the L-system string. And that also explains the other, unusual, fact that this rule has no left-hand-side! (Actually this is normal, since we are not replacing anything here (Axiom, or a symbol), but only inserting an object). The lamp is placed 50 units above the plant and then turned downwards (RU(180)) to make sure it shines on the plant. You can modify the angle to observe the effect, e.g. RU(60). Then we have to invoke the radiation model of GroIMP, just before the init() method:

//definition of an instance of LightModel:
LightModel lm = new LightModel(100000,5);

Furthermore, the Leaf module receives a parameter named al that stores the value of absorbed light after each run of the light model:

module Leaf(float al) extends Parallelogram(2,1) {
	{setShader(new AlgorithmSwitchShader(new RGBAShader(0,1,0), GREEN));}
}

Note that the shader of the leaf looks rather complicated: we are using a so-called AlgorithmSwitchShader, this is a class of shader that allows us to use one shader for the light model (“GREEN”) and another one (RGBAShader) for the visualization. We would namely like to see from the color of the leaf how much light it has absorbed. For instance, it could be dark green if it has absorbed almost nothing and very bright green or yellow if it has absorbed a lot.

In the run method, the Leaf is initiated with al = 0, thus Leaf(0).

Bud(r, p, o), (r < 10 && p == 0 && o < 4) ==> RV(-0.1) Internode Node 
		[RL(BRANCH_ANGLE) Bud(r, PHYLLOCHRON, o+1) ][ RL(LEAF_ANGLE) Leaf(0) ]  
		RH(GOLDEN_ANGLE) RV(-0.1) Internode Bud(r+1, PHYLLOCHRON, o);

Furthermore, we have to introduce a new method, absorb(), which will invoke a method of the radiation model that measures the amount of absorbed light for each leaf and writes the result to the parameter al (you can write this method after the run method):

//the new protected method absorb()
protected void absorb()
[
	lf:Leaf ::> {lf[al] = lm.getAbsorbedPower3d(lf).integrate()*2.25;
	//println(lf[al]);
 
	}
]

The rule in this method updates the parameter al of Leaf, by letting the LightModel instance lm calculate the absorbed power. As this is done separately for red, green, and blue light, we have to also invoke integrate() and multiply by a factor 2.25 to convert from W to µmol photons (the input later for the photosynthesis model). In order to see immediately how much light a leaf has absorbed we can use a println() command, it will print out the value of lf[al] into the XL console window below, however, at the moment it is commented out. Note that the println command will go to the next line after printing while a simple print command would stay on the same line.

Note that we make this method protected (like the run() method is now), because we are not going to invoke absorb() or run() directly but from another method that we will call grow():

//the new public method grow()
public void grow()
{
	run();
	lm.compute();
	absorb();
}

This method invokes run() once, then runs the light model (lm.compute()), then the rule absorb(), which measures light. We are thus dealing here with a method that does not contain L-system rules itself but that will instead invoke other methods containing L-systems. Note that this is the reason why the body of the method is surrounded by a pair of curly braces (accolades), { and }, and not square brackets, [ and ], as we know it from methods containing L-system rules. Next, we have to add in the absorb() method a command that will reset the shader of the leaf each time the method is carried out:

//the new protected method absorb()
protected void absorb()
[
	lf:Leaf ::> {lf[al] = lm.getAbsorbedPower3d(lf).integrate()*2.25;
	//println(lf[al]);
	lf.(setShader(new AlgorithmSwitchShader(new RGBAShader(lf[al]/5.0,lf[al]*2,lf[al]/100.0), GREEN)));
	}
]

Note that the shader of each leaf is now reset using the value of al to paint the leaf, thereby indicating the amount of absorbed light, a gradient of green.

Task: Do some simulations with different plant parameters (L. 60 – 63, e.g., you could change the phyllochron). Manually change the position of the lamp by selecting it in the scene and dragging it at one of the arrow tips. This can be done during the simulation. Observe the effect on light interception of the leaves.

Plotting the absorbed light in a chart

Open the file lightchart.gsz. We now want to see model output as a chart (as you might know from R). The total light intercepted by all leaves is output into a chart, and this at every step of the simulation.

For this, we have to declare a kind of data table in which the output is stored:

//insert a table here for the output:
const DatasetRef lightdata = new DatasetRef("Light intercepted by canopy");

We then have to initiate the chart in the init() method. For this, we will use the new method initChart() that will be defined later:

protected void init()
[
	//the chart is initialized here: 
	{
		initChart();
	}
 
	Axiom ==> [Plane().(setShader(WHITE))] 
		Bud(1, PHYLLOCHRON, 1);
	==>> ^ M(50) RU(180) MyLight;
]

Note that if you want to declare imperative code in a method that also contains rules (like the present init() method), then you have to put it into a pair of curly braces, { }. Also, we want to update the chart at every step, so we add a new line to the grow() method:

public void grow ()
{
	run();
	lm.compute();
	absorb();
	//update the chart here
	updateChart();
}

The latter is another new method, updateChart(), that we still need to define. The two new methods are defined, first initChart():

//definition of the method initChart(): 
protected void initChart()
{
	lightdata.clear();
	chart(lightdata, XY_PLOT);
}

This method does something with the data table lightdata, in fact it empties it at each new run, using the method clear()(L. 112). The chart command does the actual plotting: It creates an XY plot with the data table, using as x the time (simulation steps) and as y the sum of light absorbed by all leaves. The table is filled up with data by the updateChart() method:

//definition of the method updateChart(): 
protected void updateChart()
{
	lightdata.addRow().set(0, sum((* Leaf *)[al]));
}

The addRow() method of DatasetRef does the actual job, more specifically its sub-method set(), which adds the data into the 0th column of the table: The data are produced by the sum method, which searches for all leaves (* Leaf *) that are already produced and then sums up their parameter al. Searching for objects in the graph is a very powerful option in GroIMP. You can also do this at any time of the simulation by typing search commands in the XL Console, for instance the following command:

will search for all objects Leaf in the scene/graph (you have to write “Example1.Leaf” to indicate that the module was defined within the class “Example1.rgg”) the value of the parameter al and calculate the sum of it. If you omit Example1, the module Leaf cannot be found and you get an error message instead:

You can also have a look at the data table itself:

Main Menu: Panels → Explorers → Datasets

This will give you a new window:

When you double-click on the Dataset “- Light intercepted…” you get the actual table, with two columns, # and A, where the first column contains the steps, the second the sum of light intercepted by the leaves:

Introducing leaf growth

Open the file LeafGrowth.gsz.

Currently, the leaves and internodes of our plant just “pop up” with their final size, although in a real plant they would, of course, grow. Growth in length can be modelled easily: first, we need to give both Leaf and Internode several new parameters: length and age for Internode; and length, width and age for Leaf (L. 9 and L. 14).

module Internode(super.length) extends 
	Cylinder(length, 0.05) {
	{setShader(internodemat);}
}
 
module Leaf(super.length, super.width, float al) extends 
	Box(length,width,0.01).
	(setShader(new AlgorithmSwitchShader(new RGBAShader(0, 1, 0), GREEN)));

Then we initialize the length (and width) with a small value (0.1 and 0.07), and set the initial age to 1 in the init() method:

Bud(r, p, o), (r < 10 && p == 0 && o <= 3) ==> RV(-0.1) 
		Internode(0.1, 1) Node [RL(BRANCH_ANGLE) Bud(r, PHYLLOCHRON, o+1) ]
		[ RL(LEAF_ANGLE) Leaf(0.1, 0.07, 0)] RH(GOLDEN_ANGLE) RV(-0.1) 
		Internode(0.1) Bud(r+1, PHYLLOCHRON, o);	

Next, we need to find an appropriate function that describes growth. The logistic function is actually quite accurate when it comes to description of organ length. It is one of the many sigmoid functions. Its derivative can be used to describe the growth rate as a function of organ age. We declare the derivative of this logistic function as a new public method in the code, before the init() method:

//logistic function, used to determine growth rate:
public float logistic (float maxdim, int time, float phylloM, float slope)
{
	return(slope*maxdim*Math.exp(-slope*(time-phylloM)))/
	((Math.exp(-slope*(time-phylloM))+1)**2);
}

The function has three parameters, for now we are only interested in organ age. This we declare as a further (integer) parameter in Leaf and Internode:

module Internode(super.length, int age) extends 
	Cylinder(length, 0.05) {
	{setShader(internodemat);}
}
 
module Leaf(super.length, super.width, float al, int age) extends 
	Box(length,width,0.01).
	(setShader(new AlgorithmSwitchShader(new RGBAShader(0, 1, 0), GREEN)));

Next, we need to initialize age with 1 in the run() method (L. 103).

Bud(r, p, o), (r < 10 && p == 0 && o <= 3) ==> RV(-0.1) 
		Internode(0.1, 1) Node [RL(BRANCH_ANGLE) Bud(r, PHYLLOCHRON, o+1) ]
		[ RL(LEAF_ANGLE) Leaf(0.1, 0.07, 0, 1)] RH(GOLDEN_ANGLE) RV(-0.1) 
		Internode(0.1, 1) Bud(r+1, PHYLLOCHRON, o);	

Then, we extend the Leaf rule in the method that we now rename to absorbAndGrow() to model leaf ageing and extension in length and width:

protected void absorbAndGrow()
[               
	lf:Leaf ::> {
		lf[al] = lm.getAbsorbedPower3d(lf).integrate()*2.25;
		//println(lf[al]);
		lf.(setShader(new AlgorithmSwitchShader(new RGBAShader(lf[al]/5.0, lf[al]*2,lf[al]/100.0),GREEN)));
		//ageing and growth of leaf: 
		lf[age]++;
		lf[length] += logistic(3,lf[age],40,0.1);
		lf[width] = lf[length]*0.7;
	}                      
]

Finally, we add another execution (update) rule to model internode extension:

protected void absorbAndGrow()
[               
	lf:Leaf ::> {
		lf[al] = lm.getAbsorbedPower3d(lf).integrate()*2.25;
		//println(lf[al]);
		lf.(setShader(new AlgorithmSwitchShader(new RGBAShader(lf[al]/5.0, lf[al]*2,lf[al]/100.0),GREEN)));
		//ageing and growth of leaf: 
		lf[age]++;
		lf[length] += logistic(3,lf[age],40,0.1);
		lf[width] = lf[length]*0.7;
	}                      
	//extension of internode:    
	itn:Internode ::> {
		itn[age]++;
		itn[length] += logistic(1,itn[age],10,0.1);
	}
]

Task: Change the leaf growth parameters to obtain a very flat or else a rather steep light interception curve! (see figures below)

Introducing absorbing tiles

Open the file Tiles.gsz.

We also want to visualize how much light is reaching the ground. To do that, we cover the soil surface around the plant with light-absorbing tiles. First of all, you need to declare a new module:

// light absorbing tile
module Tile(float len, float wid) extends Parallelogram(len, wid)
{
	float al;
};

Note that we declare the variable al, which will be used to store the light absorbed by a tile, in a different way, namely not between parentheses but between curly braces and on a new line. We could have declared module Tile(float len, float wid, float al) …, and this would not have made a big difference. However, the method to declare variables in parentheses behind the name becomes increasingly cumbersome when we are dealing with many variables. Also, when we declare the variables in this new way, we can refer to them in a more straightforward way, for instance

t:Tile ::> t[al]

and don’t need to worry about the position of the variable in the module declaration (as was the case in parametric L-systems).

In the Axiom rule, preceding the Bud, we place 1600 (40 x 40) tiles (L. 84ff):

Axiom ==> //insert light absorbing tiles here: 
	[
		RL(90) M(4) RU(90) M(-4)
		for((1:40)) (
			for((1:40)) (
				Tile(0.25,0.25).(setShader(new RGBAShader(0.6, 0.3, 0.1)))
				)
				M(-10) RU(90) M(0.25) RU(-90)
			)
	]
 
	Bud(1, PHYLLOCHRON, 1);

Like the leaves, the tiles will become brighter when they absorb light. Update is done within the absorbAndGrow() method:

        //repainting the tiles:
	t:Tile ::> {
	t[al] = lm.getAbsorbedPower3d(t).integrate();
		//println(lf[al]);
		t.(setShader(new AlgorithmSwitchShader(
		new RGBAShader(t[al]*300, t[al]*200, t[al]),
		new RGBAShader(0.6, 0.3, 0.1))));
	}

Task: Obtain the sum of light that is absorbed by the tiles and plot it! (Hint: you can simply add a column “1” to the existing data table and then extend the method updateChart() with the sum function).

Introducing simple linear leaf photosynthesis

Open the file simpleps.gsz.

We want to have some rudimentary photosynthesis. In its simplest version, the model of photosynthesis is simply a linear function of absorbed radiation. This approach is called Radiation Use Efficiency and works well as a rough tool for many crops.

We first add another parameter, as, to the Leaf module. In this parameter, the output of our primitive photosynthesis model will be stored:

//                                                 new parameter as
module Leaf (super.length, super.width, float al, int age, float as) extends Box(length, width, 0.01).
	(setShader(new AlgorithmSwitchShader(new RGBAShader(0, 1, 0), GREEN)));

Then we define a simple conversion factor that serves to calculate the amount of assimilates in one step from the quantity of absorbed light:

// conversion factor light->assimilates:
const float CONV_FACTOR = 0.2; 

The new parameter, as, of Leaf needs to be initiated when the Leaf is first invoked, we have chosen 1 to indicate that a new Leaf starts with a little reserve of assimilates:

Bud(r, p, o), (r < 10 && p == 0 && o < 3) ==> RV(-0.1) Internode(0.1, 1) Node 
		[RL(BRANCH_ANGLE) Bud(r, PHYLLOCHRON, o+1) ][ RL(LEAF_ANGLE) Leaf(0.1, 0.07, 0, 1, 1)]  
		RH(GOLDEN_ANGLE) RV(-0.1) Internode(0.1, 1) Bud(r+1, PHYLLOCHRON, o);

The method absorbAndGrow() is modified accordingly:

protected void absorbAndGrow ()
[
	lf:Leaf ::> {
		lf[al] = lm.getAbsorbedPower3d(lf).integrate()*2.25;
		//println(lf[al]);
		lf.(setShader(new AlgorithmSwitchShader(new RGBAShader(lf[al]/5.0, lf[al]*2,lf[al]/100.0),GREEN)));
		lf[age]++;
		//amount of assimilates
		lf[as] = lf[al]*CONV_FACTOR;
		// amount of assimilates of all leaves
		float lfas = sum((* Leaf *)[as]);
		// dependency of growth on availability of assimilates
		if(lfas>0) {
		lf[length] += logistic(2,lf[age],10,0.2);
		lf[width] = lf[length]*0.7; }	
	}

Note that we also introduced a new condition to leaf growth: it is now dependent on the global availability of assimilates. Before leaf growth can occur, it is checked whether the sum of assimilates of all leaves is not zero.

Don’t forget to plot the parameter (see below)! Do you remember how to do that? And while you're at it, why don't you also plot the amount of light absorbed by the tiles?

More realistic non-linear photosynthesis

Open the file NLPS.gsz.

In this more realistic photosynthesis model, the rate of carbon fixation or carbon exchange is not directly linearly related to the light quantity but becomes saturated with increasing photosynthetic photon flux density:

where Rd = dark respiration [µmol m-2 s-1], PE = photosynthetic efficiency [-], Fmax = maximal net photosynthesis rate [µmol m-2 s-1], and CER also has the unit [µmol m-2 s-1].

We first declare the parameters of this model, they are all constants:

//parameters of the NLPS model:
const float FMAX = 20.0;
const float PHOTO_EFFICIENCY = 0.7; 
const float DARK_RESPIRATION_RATE = 0.5;

Next, a new method, calculateCER(), is created, which calculates the carbon exchange rate as a function of absorbed light and the three parameters:

//method to calculate the Carbon Exchange Rate CER:
float calculateCER(float ppfd) {
	return (float) 
	((FMAX + DARK_RESPIRATION_RATE)*PHOTO_EFFICIENCY*ppfd)
	/(PHOTO_EFFICIENCY*ppfd + FMAX + DARK_RESPIRATION_RATE) 
	- DARK_RESPIRATION_RATE;
}

This means the method calculateCER requires as input the quantity of absorbed light.

In order to obtain the actual amount of assimilates produced by any leaf with a certain area and during a certain period, we need another method, calculatePS():

//method to calculate the actual amount of assimilates produced by any leaf:
float calculatePS(float a, float ppfd, float d) {
	return calculateCER(ppfd) * a * d * 44.01e-6 
	*(180.162/264.06)
	/1000.0;
}

This method invokes the method calculateCER and multiplies it with the leaf area and a duration in time, which will be defined below, and with a number of other conversion coefficients to get the biomass in kg glucose equivalents.

You have noticed that the leaves used in this model are rectangles. However, the surface of a leaf is not equal to the product of its length and diameter but only about 65 to 70% of this. Thus, we needed to introduce a coefficient, which reduces the leaf area accordingly. This is also called a form factor:

//form factor
const float LEAF_FF = 0.6;

The amount of absorbed light is already expressed as a photon flux density; we had done the conversion earlier on. For the duration, we assume a day length of 8 hours, thus 8*60*60 seconds:

//duration:
const float DURATION = 8 * 60 * 60; 

The method absorbAndGrow() is now modified accordingly. In lf[as] we are going to store the actual cumulated amount of assimilates by invoking the calculatePS() method. Note that we are using a graph/data table lightresponse, in which we plot lf[al] against the result of the invocation of the calculateCER() method, with lf[al] as input (L. 154) (to do so, we have created a new variable cer in L. 153):

lf:Leaf ::> {
		lf[al] = lm.getAbsorbedPower3d(lf).integrate()*2.25*10;
		lf.(setShader(new AlgorithmSwitchShader(new RGBAShader(lf[al]*0.005, 
		lf[al]*0.05, lf[al]*0.001),GREEN)));
		lf[age]++;
		// functions to calculate leaf ps: 
		float area = LEAF_FF*lf[length]*lf[width]/10000;
		lf[as] += calculatePS(area,lf[al]/area,DURATION);
		float cer = calculateCER(lf[al]);
		lightresponse.addRow().set(0,lf[al],cer);
		lf[length] += logistic(2, lf[age], 10, 0.5);
		lf[width] = lf[length]*0.7;
		}
 

Run the model. Change the form factor of the leaves to 0.9 to see if this has an effect! Do the same with the parameters of the lamp and the PS model.

Aging of flowers, and fruit formation

Open FlowerAging.gsz.

As you know flowers do not stay on the plant for a long time after blooming; in fact, they perish rather quickly and are dropped altogether or are replaced by a growing fruit after successful pollination. In our model, we can very simply introduce aging and dropping of flowers. First, the Flower requires two new parameters, age and max_age (L. 20):

module Flower(int age, int max_age)  ==> //insert two new parameters for Flower
RU(180) Cone(0.3, 0.3).(setShader(internodemat))  ...

When the Flower is introduced in the run method for the first time it is initiated like this:

Bud(r, p, o), (r == 10) ==> RV(-0.1) Internode(0.05, 1) RV(-0.1) Internode(0.05, 1) Flower(1, irandom(10, 15)); 
...

The initial age is thus 1, whereas the maximum age that the flower can attain is between 10 and 15 days, as determined by this command, irandom(10, 15), which will draw a number between 10 and 15, with equal probability.

As long as age has not attained max_age, it is incremented by one, i.e. the flower ages:

//increment flower age as long as max age is not reached:
Flower(t, m), (t<m) ==> Flower(t+1, m);

When age is equal to max_age the flower is dropped:

//shed flower when max age is reached:
Flower(t,m), (t>=m) ==>  ;

Next, we could let the flower be transformed into a fruit (module already declared), with a certain probability, instead of letting it die: In order for this to work you need to write instead of

//shed flower when max age is reached:
Flower(t,m), (t>=m) ==>  ;

this:

//shed flower when max age is reached:
//Flower(t,m), (t>=m) ==>  ;
Flower(t,m), (t>=m) ==>  if(probability(0.5)) (Fruit) ;

Task: Introduce leaf shedding after 20 steps!

Dynamic crop model optimizing light interception and fruit yield

Open GrowManyFruits.gsz

This model produces a virtual plant canopy, initially consisting of 16 plants (four rows of four plants each). When you run the model, you will get the 3D visual output and also some curves (in the chart “MODEL OUTPUT”) indicating the dynamics of several yield-related state variables: light absorbed by lower and upper leaves, light absorbed by soil, number of shed leaves, number of flowers and fruits, and the assimilates produced by lower and upper leaves (where ranks 1 - 4 are lower leaves, and ranks 5 - 9 are upper leaves). At this stage you are not supposed to understand all the workings of the model, the objective of this exercice is for you to play the grower and to change a limited number of agronomic and genetic parameters (planting density, plant architecture). The parameters you may change in the code can be found in Example1.rgg, lines 5 - 13:

/* Parameters you may change: */
public @Editable @Range(min=90,max=200) float GOLDEN_ANGLE = 137.5; //137.5;      
public @Editable @Range(min=30,max=90) float BRANCH_ANGLE = 50;//50
float LEAF_ANGLE = BRANCH_ANGLE+10; //70
const float LEAF_CURVATURE = 2; //between 1 and 5
const float LEAF_FOLDING = 20; //20 between 0 and 50
public @Editable @Range(min=9,max=20) int PHYLLOCHRON = 9; //15  between 9 and 20
public @Editable @Range(min=1,max=5) int PL_NO = 4; //5 
public @Editable @Range(min=1,max=5) int ROW_NO = 4; //4
public @Editable @Range(min=1,max=10) int FINAL_LEAF_SIZE = 4; //4

However, it is also possible (and much easier?) to use the sliders for this: click on the tab Meta Objects (bottom left) then you will see in the Attribute Editor window (almost) the same parameters:

Task: run the model, and see how many fruits you get in the end! By changing plant density, can you increase the number of fruits?

Next, you may increase or decrease branching angle, leaf angle, final leaf size, leaf curvature, leaf folding, and phyllochron by up to 20 per cent to optimize fruit yield. Note that you cannot change leaf curvature and leaf folding from the slider panel, you need to modify the code and recompile.

N.B.: After having changed values with the slider, you have to press the Reset button for the new values to take effect, then press the Run Grow button. If you recompile the code (by saving Example1.rgg“), the default parameters (L. 5-12) will be chosen again!

Finally, note that this model is just a training tool (toy…), and not based on any measurements of real plants!

Assimilate Transport and Fruit Development

Open Transport.gsz

The Transport model extends the Flower aging model by implementing a physiologically-based system for sugar transport from photosynthetically active leaves through the plant architecture to developing fruits. This creates a more realistic simulation of source-sink relationships in the virtual plant.

→ Read more...

Source-sink model with central pool

At the example of a sunflower model developed as a teaching tool at the Bachelor level, we will introduce here the coupling of the previously introduced photosynthesis model with a simple allocation model based on the central pool concept and redistribution of assimilates according to the relative sink strength method introduced by Leo Marcelis. You can find a presentation with details on the model here, the sunflower model is available for download here, and the standalone photosynthesis model by Kim and Lieth is available here

Note that this is a preliminary version in which the actual growth rate is not yet fully functional. We are working on a more stable version, so stay tuned - or not ;-) !.