Master 2 BV, specialties “SEPPRO&SPP” and “Santé des Plantes - PHP”. Duration: about 2 hours. You may work alone or in pairs.
At the end of this session you should be able to:
You do not need to be a programmer. Every code change you are asked to make is given or guided step by step.
| Part | Content | Time |
|---|---|---|
| 0 | Setup: open and start the model | 10 min |
| 1 | The biology in brief | 10 min |
| 2 | The model at a glance | 15 min |
| 3 | Observe the reference run | 15 min |
| 4 | Reading the code: production, transport, use | 25 min |
| 5 | Virtual experiments | 25 min |
| 6 | Your specialty: plant health or seed science | 15 min |
| 7 | Synthesis | 5 min |
| (Bonus) | Let organs catch up: developmental plasticity | for fast groups |
Transport.gsz (File → Open).Example1.rgg. Open it in the text editor (Panels → Explorers → Files, then double-click the file).grow. Each click on grow = one time step = one hour of simulated time.init() method). Every time you save the code (Ctrl+S), the model is also recompiled and reset.grow a few times, then start a long run (run/loop button). Let it run to about step 600 (≈ 25 days) while you work on Parts 1 and 2. You will need the result in Part 3.Four charts open automatically:
| Chart | What it shows |
|---|---|
| Light intercepted by canopy | total light absorbed by all leaves during the last hour |
| Canopy photosynthesis | total amount of sugar stored in all leaves, in mg (a stock, not the hourly production!) |
| Fruit growth | size of every fruit over time (one series per fruit) |
| Distribution of sugar in the internodes | sugar content (mg) of every internode at the current step, plotted against its rank |
Colour code in the 3D view (false colours, for display only):
The XL Console. Messages from the model appear here. You can also type queries into it, for example:
((* Example1.Fruit *)[size]) // size of every fruit ((* Example1.Fruit *)[as]) // sugar stock of every fruit count((* Example1.Fruit *)) // number of fruits
Plants produce sugars (assimilates) in source organs – mainly mature leaves – and use them in sink organs – young leaves, stems, roots, flowers, fruits and seeds. Sugars travel through the phloem. In the widely accepted Münch model, sugar loading at the source raises the osmotic pressure there, unloading at the sink lowers it, and the resulting pressure difference drives a mass flow from source to sink. A young leaf is first a sink and becomes a source when it reaches roughly a third to half of its final size (sink–source transition).
Questions (answer in 2–3 lines each):
| Module | Role | Important attributes |
|---|---|---|
Bud | apical meristem; produces new metamers | rank (position on the axis), phyllo (countdown until next metamer), order (1 = main stem, 2 = branch, 3 = dormant bud) |
Internode | stem segment; transport pathway and sink | length, age, rank, as (assimilate content, mg) |
Node | attachment point of a leaf and a lateral bud | – |
Leaf | source (and sink while young) | length, width, al (absorbed light), age, as (assimilate content, mg) |
Flower | becomes a fruit if enough sugar is available, otherwise it is shed | age, max_age |
Fruit | main sink | size, age, as, no (fruit number) |
Tile | light-absorbing ground | al |
MyLight | a 200 W spotlight 50 units above the plant | – |
public void grow () { run(); // 1. architecture: new metamers, flowers, fruit set, leaf death lm.compute(); // 2. light model: ray tracing of the whole scene absorbAndGrow(); // 3. light absorption, photosynthesis, organ growth //if(time % 24 == 0) { //for(int hour = 0; hour < 24; hour++) { transport(); // 4. one round of transport per hour // } //} updateChart(); // 5. charts time++; }
Q4. In an earlier version of the model, the lines now commented out were active: transport happened 24 times in a row, but only once every 24 steps. A young leaf is a sink only during its first ~13 hours. What problem did this cause? Why is it important that the time step of transport matches the time scale of the processes it feeds?
The rule that makes a bud produce a new metamer is:
Bud(r, p, o), (r < 10 && p == 0 && o < 3) ==> RV(-0.1) Internode(0.1, 1, r, (r==1 && o==1) ? 0.2 : 0.0) Node [ RL(BRANCH_ANGLE) Bud(r, PHYLLOCHRON, o+1) ] // lateral bud -> branch [ LFA(1) Leaf(0.1, 0.07, 0, 1, 0, r) ] // leaf RH(GOLDEN_ANGLE) RV(-0.1) Internode(0.1, 1, r, (r==1 && o==1) ? 0.1 : 0.0) Bud(r+1, PHYLLOCHRON, o); // the apex continues
Q5. Draw (on paper) one metamer as produced by this rule: which organs, in which order, and what is inside the square brackets [ ]?
Q6. When rank reaches 10, the bud turns into a Flower. How many flowers do you expect on the plant? (Hint: look at the condition o < 3 and at the order given to the main stem in init().)
Q7. Only the first two internodes of the main stem (r==1 && o==1) start with some sugar; all other new organs start empty. What does this initial sugar represent biologically? Why is it a good idea not to give every new organ a “start capital”?
Look at your run (about 600 steps). Use the 3D view, the charts and the console queries.
Fill in:
| Observation | Your answer |
|---|---|
| Step at which the first flower appears | |
| Step at which the first fruit appears | |
| Number of flowers / number of fruits | |
| Final size of each fruit (console query) | |
| Shape of the fruit growth curves (linear? S-shaped? all the same?) | |
| Sugar distribution in the internodes: at which ranks is it highest? | |
| Which leaves absorb the most light? |
Q8. The internode sugar chart shows the highest sugar content in the middle ranks, not at the base or at the top. Propose an explanation. (Think about which leaves are mature and well lit, and which organs at the top are consuming sugar.)
We now follow a sugar molecule from where it is made to where it is used.
float calculateCER(float ppfd) { return ((FMAX + DARK_RESPIRATION_RATE) * PHOTO_EFFICIENCY * ppfd) / (PHOTO_EFFICIENCY * ppfd + FMAX + DARK_RESPIRATION_RATE) - DARK_RESPIRATION_RATE; }
ppfd is the photon flux density received by the leaf (µmol photons m⁻² s⁻¹), CER is the CO₂ exchange rate (µmol CO₂ m⁻² s⁻¹). FMAX = 20, DARK_RESPIRATION_RATE = 0.5, PHOTO_EFFICIENCY = 0.85.
calculatePS converts CER into milligrams of glucose produced by one leaf in one hour. Which quantities is CER multiplied by? (Look at the code.)
In absorbAndGrow(), every leaf adds its production to its own stock: lf[as] += calculatePS(…).
The method transport() contains three rules. The rate constants are defined at the top of the file:
const float LEAF_EXPORT = 0.05; // fraction of leaf stock exported per hour const float D_PHLOEM = 0.2; // exchange between adjacent internodes const float FRUIT_UNLOAD = 0.1; // unloading into the fruit per hour
Rule 1 – between a leaf and the internode that carries it
lf:Leaf -ancestor-> itn:Internode ::> { boolean sink = lf[length] < 1.0; // about 1/3 of final length if (!sink && lf[as] > 0.001) { // mature leaf: EXPORT float r = LEAF_EXPORT * lf[as]; lf[as] -= r; itn[as] += r; } else if (sink && itn[as] > 0.001) { // young leaf: IMPORT float r = LEAF_EXPORT * itn[as]; lf[as] += r; itn[as] -= r; } }
Rule 2 – between two successive internodes
i_top:Internode -ancestor-> i_bottom:Internode ::> { float r = D_PHLOEM * (i_top[as] - i_bottom[as]); i_bottom[as] :+= r; i_top[as] :-= r; }
Rule 3 – from an internode into the fruit it carries
itn:Internode -successor-> fr:Fruit ::> { float r = FRUIT_UNLOAD * Math.max(0, itn[as] - fr[as]); itn[as] :-= r; fr[as] :+= r; }
Reading tips: a -ancestor→ b:Internode means “starting from a, go down towards the base and take the first Internode you meet”. a -successor→ b means “b follows a directly”. The operators :+= and :-= collect all changes and apply them together at the end of the step, so the result does not depend on the order in which the rules are applied.
Internodes and fruits grow with the same logic. Here is the internode version:
itn:Internode ::> { itn[age]++; itn[as] *= (1 - MR); // maintenance respiration float potential = logistic(INT_MAX_LENGTH, itn[age], 20, 0.2); // growth if sugar were unlimited float demand = potential * GROWTH_COST; // sugar needed for that growth float f = (demand > 0) ? Math.min(1.0, itn[as] / demand) : 0; // fraction of demand satisfied itn[length] += potential * f; // actual growth itn[as] -= demand * f; // sugar consumed }
The fruit block is identical, with FRUIT_MAX_SIZE and FRUIT_COST.
potential, demand and f are. What is the value of f when the organ has more sugar than it needs? When it has none?INT_MAX_LENGTH × GROWTH_COST = 1.5 mg of sugar for its whole growth. Compare with the values in the internode sugar chart. Is this plant limited by its sources or by its sinks?fl:Flower(t, m)(* -ancestor-> itn:Internode *), (t >= m && t < m+2) ==> { float sugar = itn[as]; println("flower sugar: " + sugar); } if (sugar > FRUIT_SET_THRESHOLD) ( {noFrts++;} Fruit(0.01, 1, 0.1, noFrts) );
Q21. In words: under which condition does a flower become a fruit? Where is the sugar measured? What happens to a flower when the condition is not met? (Hint: what is on the right-hand side of the rule in that case?)
Method, for each experiment:
A run takes several minutes, so the experiments are shared out between the pairs. Your teacher will tell you which ones to do; the results are pooled at the end.
| Exp. | What to change | Question behind it |
|---|---|---|
| E1 | D_PHLOEM: 0.2 → 0.02 | How far can sugar travel in the stem? |
| E2 | MR: 0.002 → 0.02 | What does respiration cost the plant? |
| E3 | LEAF_EXPORT: 0.05 → 0.01 | What if leaves keep their sugar? |
| E4 | Lamp power: setPower(200.0) → 100.0 (module MyLamp) | Source limitation |
| E5 | FRUIT_COST: 250 → 500 | A more demanding sink |
| E6 | Sink–source transition: lf[length] < 1.0 → < 2.0 (Rule 1) | Longer sink phase of young leaves |
| E7 | FRUIT_SET_THRESHOLD: 3.5 → 1.0, then → 10 | Fruit set vs. abortion |
Results table (one line per run):
| Exp. | Value | Prediction | Nb fruits | Mean fruit size | Internode sugar profile | Plant height / internode length | Explanation |
|---|---|---|---|---|---|---|---|
| Ref | – | – | |||||
Q22. Which experiments changed fruit size, and which changed fruit number? Why are these two responses controlled by different parts of the model?
Q23. Was any result the opposite of your prediction? Explain it using the code.
Choose one track and do at least one task.
A1. Aphids: a new phloem sink. Aphids feed directly on phloem sap. Add a colony on the internodes of rank 3, starting at step 100.
At the top of the file, next to the other constants, add:
const float APHID_RATE = 0.02; // fraction of internode sugar taken per hour float aphidSugar = 0; // total sugar taken by the aphids
Inside transport(), add a fourth rule (before the closing ]):
itn:Internode, (itn[rank] == 3 && time > 100) ::> { float r = APHID_RATE * itn[as]; itn[as] :-= r; aphidSugar += r; }
Print the total at each step: add println(“aphids: ” + aphidSugar); in grow(). Compare with the reference run. Then move the colony to rank 9, just below the flowers.
A2. Defoliation by a leaf disease. Remove all leaves of rank ≤ 4 at step 200. Add this rule in run():
lf:Leaf, (time == 200 && lf[rank] <= 4) ==> ;
A3. A pathogen that lowers photosynthetic capacity (e.g. a leaf spot or a virus): reduce FMAX from 20 to 10.
Questions:
B1. Fruit thinning. Remove every second fruit at step 360. Add this rule in run():
fr:Fruit, (time == 360 && fr[no] % 2 == 0) ==> ;
Compare the final size of the remaining fruits with the reference run.
B2. Fruit set threshold. Run experiment E7 if nobody else has. How do the number of fruits and the mean fruit size change together?
B3. Variable developmental speed. In the reference plant, all branches develop in step, so all fruits have almost the same age. Make development less regular: in the metamer rule (Part 2.3), replace both occurrences of PHYLLOCHRON by irandom(PHYLLOCHRON-8, PHYLLOCHRON+8). Run the model twice and compare the spread of fruit sizes (largest minus smallest) with the reference run.
Questions:
For fast groups. At present an internode's potential growth depends on its calendar age (Q17): if it is starved during its growth window, the loss is permanent. Real organs can often delay their development instead.
Hints:
Internode a new variable for its developmental age: change the module declaration to module Internode(super.length, int age, int rank, float as) extends Cylinder(length, 0.1) { float dev; }.itn[dev] instead of itn[age] to compute the potential.itn[dev] += f;logistic expects an int for time. Change its declaration to float time.Compare the internode lengths along the main stem with the reference run. Where does the change have the largest effect?
This exercise is not handed in, but the questions above cover the kind of reasoning expected in the exam: explaining source–sink relations, reading a simple model rule, and predicting and interpreting the outcome of a virtual experiment. Keep your answers and tables as revision notes.