1.3.77
 
Loading...
Searching...
No Matches
CarbohydrateModel.cpp
Go to the documentation of this file.
1
16#include "PlantArchitecture.h"
17#include "../include/PlantArchitecture.h"
18
19using namespace helios;
20
21
22float Phytomer::calculatePhytomerConstructionCosts() const {
23 float leaf_carbon_percentage = plantarchitecture_ptr->plant_instances.at(this->plantID).carb_parameters.leaf_carbon_percentage;
24 float SLA = plantarchitecture_ptr->plant_instances.at(this->plantID).carb_parameters.SLA;
25
26 float leaf_construction_cost_base = leaf_carbon_percentage * 0.1 / (C_molecular_wt * SLA); // mol C/m^2
27
28 float phytomer_carbon_cost = 0.f; // mol C
29
30 // leaves (cost per area basis)
31 float leaf_area = 0;
32 uint p = 0;
33 for (const auto &petiole: leaf_objIDs) {
34 for (uint leaf_objID: petiole) {
35 if (context_ptr->doesObjectExist(leaf_objID)) {
36 float obj_area = context_ptr->getObjectArea(leaf_objID);
37 float scale_factor = current_leaf_scale_factor.at(p);
38 float scaled_area = obj_area / powi(scale_factor, 2);
39 leaf_area += scaled_area;
40 }
41 }
42 p++;
43 }
44 phytomer_carbon_cost += leaf_construction_cost_base * leaf_area;
45
46 return phytomer_carbon_cost;
47}
48
49float Phytomer::calculateFlowerConstructionCosts(const FloralBud &fbud) const {
50 float flower_carbon_cost = 0.f; // mol C
51
52 for (uint flower_objID: fbud.inflorescence_objIDs) {
53 flower_carbon_cost += plantarchitecture_ptr->plant_instances.at(this->plantID).carb_parameters.total_flower_cost;
54 }
55
56 return flower_carbon_cost;
57}
58
59void PlantArchitecture::initializeCarbohydratePool(float carbohydrate_concentration_molC_m3) const {
60 for (auto &plant: plant_instances) {
61 auto shoot_tree = &plant.second.shoot_tree;
62
63 for (auto &shoot: *shoot_tree) {
64 // calculate shoot volume
65 float shoot_volume = shoot->calculateShootInternodeVolume();
66 // set carbon pool
67 shoot->sugar_pool_molC = shoot_volume * carbohydrate_concentration_molC_m3;
68 context_ptr->setObjectData(shoot->internode_tube_objID, "carbohydrate_concentration", carbohydrate_concentration_molC_m3);
69 context_ptr->setObjectData(shoot->internode_tube_objID, "sugar_pool_molC", shoot->sugar_pool_molC);
70 }
71 }
72}
73
74void PlantArchitecture::initializePlantCarbohydratePool(uint plantID, float carbohydrate_concentration_molC_m3) {
75
76 // Make sure that the plant exists in the context
77 if (plant_instances.find(plantID) == plant_instances.end()) {
78 helios_runtime_error("ERROR (PlantArchitecture::initializePlantCarbohydratePool): Plant with ID of " + std::to_string(plantID) + " does not exist.");
79 } else if (carbohydrate_concentration_molC_m3 < 0.f) {
80 helios_runtime_error("ERROR (PlantArchitecture::initializePlantCarbohydratePool): Carbohydrate concentration must be greater than or equal to zero.");
81 }
82
83 // loop over all shoots
84 for (auto &shoot: plant_instances.at(plantID).shoot_tree) {
85 initializeShootCarbohydratePool(plantID, shoot->ID, carbohydrate_concentration_molC_m3);
86 }
87}
88
89void PlantArchitecture::initializeShootCarbohydratePool(uint plantID, uint shootID, float carbohydrate_concentration_molC_m3) {
90 if (plant_instances.find(plantID) == plant_instances.end()) {
91 helios_runtime_error("ERROR (PlantArchitecture::initializeShootCarbohydratePool): Plant with ID of " + std::to_string(plantID) + " does not exist.");
92 } else if (shootID >= plant_instances.at(plantID).shoot_tree.size()) {
93 helios_runtime_error("ERROR (PlantArchitecture::initializeShootCarbohydratePool): Shoot with ID of " + std::to_string(shootID) + " does not exist.");
94 } else if (carbohydrate_concentration_molC_m3 < 0.f) {
95 helios_runtime_error("ERROR (PlantArchitecture::initializeShootCarbohydratePool): Carbohydrate concentration must be greater than or equal to zero.");
96 }
97
98 // calculate shoot volume
99 float shoot_volume = plant_instances.at(plantID).shoot_tree.at(shootID)->calculateShootInternodeVolume();
100
101 // set carbon pool
102 plant_instances.at(plantID).shoot_tree.at(shootID)->sugar_pool_molC = shoot_volume * carbohydrate_concentration_molC_m3;
103}
104
106 for (const auto &[plantID, plant_instance]: plant_instances) {
107 auto shoot_tree = &plant_instance.shoot_tree;
108
109 for (auto &shoot: *shoot_tree) {
110 // Set cumulative photosynthesis of dormant shoots equal to zero.
111 if (shoot->isdormant) {
112 for (const auto &phytomer: shoot->phytomers) {
113 for (const auto &leaf_objID: flatten(phytomer->leaf_objIDs)) {
114 for (uint UUID: context_ptr->getObjectPrimitiveUUIDs(leaf_objID)) {
115 context_ptr->setPrimitiveData(UUID, "cumulative_net_photosynthesis", 0);
116 }
117 }
118 }
119 } else {
120 for (const auto &phytomer: shoot->phytomers) {
121 for (auto &leaf_objID: flatten(phytomer->leaf_objIDs)) {
122 for (uint UUID: context_ptr->getObjectPrimitiveUUIDs(leaf_objID)) {
123 float lUUID_area = context_ptr->getPrimitiveArea(UUID);
124 float current_net_photo = 0;
125
126 float leaf_A = 0.f;
127 if (context_ptr->doesPrimitiveDataExist(UUID, "net_photosynthesis")) {
128 context_ptr->getPrimitiveData(UUID, "net_photosynthesis", leaf_A);
129 }
130
131 float new_hourly_photo = leaf_A * lUUID_area * 3600.f * 1e-6f; //net photosynthesis (mol C hr-1) from umol CO2 m-2 sec-1
132
133 if (context_ptr->doesPrimitiveDataExist(UUID, "cumulative_net_photosynthesis")) {
134 context_ptr->getPrimitiveData(UUID, "cumulative_net_photosynthesis", current_net_photo);
135 }
136 current_net_photo += new_hourly_photo;
137 context_ptr->setPrimitiveData(UUID, "cumulative_net_photosynthesis", current_net_photo);
138 }
139 }
140 }
141 }
142 }
143 }
144}
145
146void PlantArchitecture::accumulateShootPhotosynthesis() const {
147 uint A_prim_data_missing = 0;
148
149 for (auto &[plantID, plant_instance]: plant_instances) {
150
151 const auto shoot_tree = &plant_instance.shoot_tree;
152
153 for (const auto &shoot: *shoot_tree) {
154 uint shootID = shoot->ID;
155 float net_photosynthesis = 0;
156 float shoot_volume = plant_instances.at(plantID).shoot_tree.at(shoot->ID)->calculateShootInternodeVolume();
157
158 if (shoot->isdormant) {
159 for (const auto &phytomer: shoot->phytomers) {
160 for (const auto &leaf_objID: flatten(phytomer->leaf_objIDs)) {
161 for (uint UUID: context_ptr->getObjectPrimitiveUUIDs(leaf_objID)) {
162 context_ptr->setPrimitiveData(UUID, "cumulative_net_photosynthesis", 0.f);
163 }
164 }
165 }
166 } else {
167 for (const auto &phytomer: shoot->phytomers) {
168 for (const auto &leaf_objID: flatten(phytomer->leaf_objIDs)) {
169 for (uint UUID: context_ptr->getObjectPrimitiveUUIDs(leaf_objID)) {
170 if (context_ptr->doesPrimitiveDataExist(UUID, "cumulative_net_photosynthesis")) {
171 float A;
172 context_ptr->getPrimitiveData(UUID, "cumulative_net_photosynthesis", A);
173 net_photosynthesis += A;
174 context_ptr->setPrimitiveData(UUID, "cumulative_net_photosynthesis", 0.f);
175 } else {
176 A_prim_data_missing++;
177 context_ptr->setPrimitiveData(UUID, "cumulative_net_photosynthesis", 0.f);
178 }
179 }
180 }
181 }
182 }
183 if (net_photosynthesis >= 0.f) {
184 shoot->sugar_pool_molC += net_photosynthesis;
185 }
186 float total_carbohydrate = shoot->sugar_pool_molC + shoot->starch_pool_molC;
187 context_ptr->setObjectData(shoot->internode_tube_objID, "carbohydrate_concentration", total_carbohydrate / shoot_volume);
188 context_ptr->setObjectData(shoot->internode_tube_objID, "sugar_pool_molC", shoot->sugar_pool_molC);
189 context_ptr->setObjectData(shoot->internode_tube_objID, "total_carbohydrate_pool_molC", total_carbohydrate);
190 context_ptr->setObjectData(shoot->internode_tube_objID, "daily_net_photosynthesis", net_photosynthesis);
191 }
192 }
193
194 if (A_prim_data_missing > 0) {
195 std::cerr << "WARNING (PlantArchitecture::accumulateShootPhotosynthesis): " << A_prim_data_missing << " leaf primitives were missing net_photosynthesis primitive data. Did you run the photosynthesis model?" << std::endl;
196 }
197}
198
199void PlantArchitecture::subtractShootMaintenanceCarbon(float dt) const {
200 for (const auto &[plantID, plant_instance]: plant_instances) {
201 auto shoot_tree = &plant_instance.shoot_tree;
202
203 const CarbohydrateParameters &carbohydrate_params = plant_instances.at(plantID).carb_parameters;
204
205 float rho_cw = carbohydrate_params.stem_density * carbohydrate_params.stem_structural_carbon_percentage / C_molecular_wt; // Density of carbon in almond wood (mol C m^-3)
206
207 for (auto &shoot: *shoot_tree) {
208 if (context_ptr->doesObjectExist(shoot->internode_tube_objID)) {
209 if (shoot->old_shoot_volume >= 0.f) {
210 shoot->sugar_pool_molC -= shoot->old_shoot_volume * rho_cw * plant_instances.at(plantID).stem_maintenance_respiration_rate * dt; // remove shoot maintenance respiration
211 shoot->sugar_pool_molC -= shoot->old_shoot_volume * rho_cw * plant_instances.at(plantID).root_maintenance_respiration_rate / carbohydrate_params.shoot_root_ratio * dt; // remove root maintenance respiration portion
212 float net_respiration = (shoot->old_shoot_volume * rho_cw * plant_instances.at(plantID).stem_maintenance_respiration_rate * dt) + (shoot->old_shoot_volume * rho_cw * plant_instances.at(plantID).root_maintenance_respiration_rate / carbohydrate_params.shoot_root_ratio * dt);
213 context_ptr->setObjectData(shoot->internode_tube_objID, "daily_respiration", net_respiration);
214 }
215 }
216 }
217 }
218}
219
220void PlantArchitecture::subtractShootGrowthCarbon() {
221 for (auto &[plantID, plant_instance]: plant_instances) {
222 const auto shoot_tree = &plant_instance.shoot_tree;
223
224 const CarbohydrateParameters &carbohydrate_params = plant_instances.at(plantID).carb_parameters;
225
226 float rho_cw = carbohydrate_params.stem_density * carbohydrate_params.stem_structural_carbon_percentage / C_molecular_wt; // Mature density of carbon in almond wood (mol C m^-3)
227
228 for (const auto &shoot: *shoot_tree) {
229 float shoot_volume = plant_instances.at(plantID).shoot_tree.at(shoot->ID)->calculateShootInternodeVolume();
230 uint parentID = shoot->parent_shoot_ID;
231 float shoot_growth = 0;
232 float shoot_growth_respiration = 0;
233
234 for (int p = 0; p < shoot->phytomers.size(); p++) {
235 float phytomer_volume = plant_instances.at(plantID).shoot_tree.at(shoot->ID)->phytomers.at(p)->calculatePhytomerVolume(p);
236 phytomer_volume = std::clamp(phytomer_volume, 0.f, phytomer_volume);
237 float phytomer_age = plant_instances.at(plantID).shoot_tree.at(shoot->ID)->phytomers.at(p)->age;
238 float density_dynamic = std::clamp(phytomer_age / carbohydrate_params.maturity_age, 0.f, 1.f);
239 // Clamp dynamic carbon density between minimum value and density at full maturity
240 float rho_cw_dynamic = rho_cw * (carbohydrate_params.initial_density_ratio + (1 - carbohydrate_params.initial_density_ratio) * density_dynamic); // Carbon density of the stem for the given phytomer (mol C / m^3 wood)
241 float phytomer_growth_carbon_demand = 0.f;
242 if (plant_instances.at(plantID).shoot_tree.at(shoot->ID)->old_shoot_volume > 1*powf(10, -6)){
243 phytomer_growth_carbon_demand = rho_cw_dynamic * (phytomer_volume - plant_instances.at(plantID).shoot_tree.at(shoot->ID)->phytomers.at(p)->old_phytomer_volume); // Structural carbon - mol C / m^3 wood
244 shoot->sugar_pool_molC -= phytomer_growth_carbon_demand ; // Subtract construction carbon from the shoot's carbon pool
245 shoot->sugar_pool_molC -= phytomer_growth_carbon_demand / carbohydrate_params.shoot_root_ratio; // Subtract construction carbon for the roots from the carbon pool
246 shoot_growth += (phytomer_growth_carbon_demand ) + (phytomer_growth_carbon_demand / carbohydrate_params.shoot_root_ratio);
247 shoot->sugar_pool_molC -= phytomer_growth_carbon_demand * carbohydrate_params.growth_respiration_fraction; // Subtract growth respiration carbon from the shoot's carbon pool
248 shoot->sugar_pool_molC -= phytomer_growth_carbon_demand * carbohydrate_params.growth_respiration_fraction / carbohydrate_params.shoot_root_ratio;
249 shoot_growth_respiration += (phytomer_growth_carbon_demand * carbohydrate_params.growth_respiration_fraction) + (phytomer_growth_carbon_demand * carbohydrate_params.growth_respiration_fraction / carbohydrate_params.shoot_root_ratio);
250 }else if (plant_instances.at(plantID).shoot_tree.at(shoot->ID)->old_shoot_volume > 0 && shoot->sugar_pool_molC > rho_cw_dynamic * (phytomer_volume - plant_instances.at(plantID).shoot_tree.at(shoot->ID)->phytomers.at(p)->old_phytomer_volume))
251 {
252 phytomer_growth_carbon_demand = rho_cw_dynamic * (phytomer_volume - plant_instances.at(plantID).shoot_tree.at(shoot->ID)->phytomers.at(p)->old_phytomer_volume); // Structural carbon - mol C / m^3 wood
253 shoot->sugar_pool_molC -= phytomer_growth_carbon_demand ; // Subtract construction carbon from the shoot's carbon pool
254 shoot->sugar_pool_molC -= phytomer_growth_carbon_demand / carbohydrate_params.shoot_root_ratio; // Subtract construction carbon for the roots from the carbon pool
255 shoot_growth += (phytomer_growth_carbon_demand ) + (phytomer_growth_carbon_demand / carbohydrate_params.shoot_root_ratio);
256 shoot->sugar_pool_molC -= phytomer_growth_carbon_demand * carbohydrate_params.growth_respiration_fraction; // Subtract growth respiration carbon from the shoot's carbon pool
257 shoot->sugar_pool_molC -= phytomer_growth_carbon_demand * carbohydrate_params.growth_respiration_fraction / carbohydrate_params.shoot_root_ratio;
258 shoot_growth_respiration += (phytomer_growth_carbon_demand * carbohydrate_params.growth_respiration_fraction) + (phytomer_growth_carbon_demand * carbohydrate_params.growth_respiration_fraction / carbohydrate_params.shoot_root_ratio);
259 }else{
260 phytomer_growth_carbon_demand = rho_cw_dynamic * phytomer_volume;
261 plant_instances.at(plantID).shoot_tree.at(parentID)->sugar_pool_molC -= phytomer_growth_carbon_demand;
262 plant_instances.at(plantID).shoot_tree.at(parentID)->sugar_pool_molC -= phytomer_growth_carbon_demand / carbohydrate_params.shoot_root_ratio;
263 shoot_growth += (phytomer_growth_carbon_demand ) + (phytomer_growth_carbon_demand / carbohydrate_params.shoot_root_ratio);
264 plant_instances.at(plantID).shoot_tree.at(parentID)->sugar_pool_molC -= (phytomer_growth_carbon_demand * carbohydrate_params.growth_respiration_fraction) + (phytomer_growth_carbon_demand * carbohydrate_params.growth_respiration_fraction / carbohydrate_params.shoot_root_ratio);
265 shoot_growth_respiration += (phytomer_growth_carbon_demand * carbohydrate_params.growth_respiration_fraction) + (phytomer_growth_carbon_demand * carbohydrate_params.growth_respiration_fraction / carbohydrate_params.shoot_root_ratio);
266 shoot->sugar_pool_molC += carbohydrate_params.stem_carbohydrate_percentage * phytomer_growth_carbon_demand;
267 }
268
269 plant_instances.at(plantID).shoot_tree.at(shoot->ID)->phytomers.at(p)->old_phytomer_volume = phytomer_volume; // Update the old volume of the phytomer
270 }
271
272 // Update shoot's carbohydrate_concentration value (mol C / m^-3)
273 if (context_ptr->doesObjectExist(shoot->internode_tube_objID)) {
274 float total_carbohydrate = shoot->sugar_pool_molC + shoot->starch_pool_molC;
275 context_ptr->setObjectData(shoot->internode_tube_objID, "carbohydrate_concentration", total_carbohydrate / shoot_volume);
276 context_ptr->setObjectData(shoot->internode_tube_objID, "sugar_pool_molC", shoot->sugar_pool_molC);
277 context_ptr->setObjectData(shoot->internode_tube_objID, "total_carbohydrate_pool_molC", total_carbohydrate);
278 context_ptr->setObjectData(shoot->internode_tube_objID, "daily_growth", shoot_growth);
279 context_ptr->setObjectData(shoot->internode_tube_objID, "daily_growth_respiration", shoot_growth_respiration);
280 }
281 }
282 }
283}
284
285
286float Phytomer::calculateFruitConstructionCosts(const FloralBud &fbud) const {
287 const CarbohydrateParameters &carbohydrate_params = plantarchitecture_ptr->plant_instances.at(this->plantID).carb_parameters;
288
289 float fruit_construction_cost_base = carbohydrate_params.fruit_density * carbohydrate_params.fruit_carbon_percentage / C_molecular_wt; // mol C/m^3
290
291 float fruit_carbon_cost = 0.f; // mol C
292
293 // fruit (cost per fruit basis)
294 for (uint fruit_objID: fbud.inflorescence_objIDs) {
295 float mature_volume = context_ptr->getPolymeshObjectVolume(fruit_objID) / fbud.current_fruit_scale_factor; // mature fruit volume
296 fruit_carbon_cost += fruit_construction_cost_base * (mature_volume) * (fbud.current_fruit_scale_factor - fbud.previous_fruit_scale_factor);
297 }
298
299 return fruit_carbon_cost;
300}
301
302void PlantArchitecture::checkCarbonPool_abortOrgans(float dt) {
303 for (auto &[plantID, plant_instance]: plant_instances) {
304 const auto shoot_tree = &plant_instance.shoot_tree;
305 const CarbohydrateParameters &carbohydrate_params = plant_instances.at(plantID).carb_parameters;
306
307 float fruit_construction_cost_base = carbohydrate_params.fruit_density * carbohydrate_params.fruit_carbon_percentage / C_molecular_wt; // mol C/m^3
308
309 for (const auto &shoot: *shoot_tree) {
310 uint shootID = shoot->ID;
311
312 shoot->total_carbohydrate_pool_molC = shoot->sugar_pool_molC + shoot->starch_pool_molC;
313
314 // Prevent any shoots from reaching negative carbon values: instant death
315 if (shoot->total_carbohydrate_pool_molC <= 0.f) {
316 pruneBranch(plantID, shootID, 0);
317 continue;
318 }
319
320 float shoot_volume = plant_instances.at(plantID).shoot_tree.at(shootID)->calculateShootInternodeVolume();
321 // Establish a working carbon pool to see if you could stay above the carbon threshold by aborting fruits.
322 float working_carb_pool = shoot->total_carbohydrate_pool_molC;
323 if (dt > carbohydrate_params.bud_death_threshold_days) {
324 shoot->days_with_negative_carbon_balance += dt / 2; // Make sure you have at least two timesteps before starting to abort buds
325 } else {
326 shoot->days_with_negative_carbon_balance += dt;
327 }
328
329 // No need to mess with anything if you already have a carbon surplus
330 if (shoot->total_carbohydrate_pool_molC > carbohydrate_params.carbohydrate_abortion_threshold * carbohydrate_params.stem_density * shoot_volume / C_molecular_wt) {
331 shoot->days_with_negative_carbon_balance = 0;
332 continue;
333 }
334
335 // Prune branches that can't stay above a sustainable threshold
336 if (shoot->total_carbohydrate_pool_molC < carbohydrate_params.carbohydrate_pruning_threshold * carbohydrate_params.stem_density * shoot_volume / C_molecular_wt &&
337 shoot->days_with_negative_carbon_balance > carbohydrate_params.branch_death_threshold_days) {
338 pruneBranch(plantID, shootID, 0);
339 continue;
340 }
341 // Keep track of how many days the shoot has been below the threshold
342 if (shoot->days_with_negative_carbon_balance <= carbohydrate_params.bud_death_threshold_days) {
343 continue;
344 }
345
346 // Loop over fruiting buds and abort them one at a time until you would be able to stay above the threshold
347 const auto phytomers = &shoot->phytomers;
348
349 bool living_buds = true;
350 while (living_buds) {
351 living_buds = false;
352
353 for (const auto &phytomer: *phytomers) {
354 bool next_phytomer = false;
355
356 for (auto &petiole: phytomer->floral_buds) {
357 if (next_phytomer) {
358 break;
359 }
360
361 bool next_petiole = false;
362 for (auto &fbud: petiole) {
363 if (next_petiole) {
364 break;
365 }
366 if (fbud.state == BUD_DORMANT || fbud.state == BUD_DEAD) {
367 continue;
368 }
369
370 for (uint fruit_objID: fbud.inflorescence_objIDs) {
371 float mature_volume = context_ptr->getPolymeshObjectVolume(fruit_objID) / fbud.current_fruit_scale_factor; // mature fruit volume
372 working_carb_pool += fruit_construction_cost_base * (mature_volume) * (fbud.current_fruit_scale_factor - fbud.previous_fruit_scale_factor);
373 }
374 phytomer->setFloralBudState(BUD_DEAD, fbud);
375 // Kill a floral bud to eliminate it as a future sink
376
377 if (working_carb_pool > carbohydrate_params.carbohydrate_abortion_threshold * carbohydrate_params.stem_density * shoot_volume / C_molecular_wt) {
378 goto shoot_balanced;
379 } // If the amount of carbon you've eliminated by aborting flower buds would have given you a positive carbon balance, move on to the next shoot
380
381 living_buds = true;
382 // There was at least one living bud, so stay in the loop until there aren't any more
383 next_petiole = true;
384 // As soon as you've eliminated one bud from a given petiole, move to the next one
385 next_phytomer = true;
386 // As soon as you've eliminated one bud from a given phytomer, move to the next one
387 }
388 }
389 }
390 }
391 shoot_balanced:; // empty statement after the label to avoid a compiler warning
392 }
393 }
394}
395
396
397void PlantArchitecture::checkCarbonPool_adjustPhyllochron(float dt) {
398 for (auto &[plantID, plant_instance]: plant_instances) {
399 const auto shoot_tree = &plant_instance.shoot_tree;
400 const CarbohydrateParameters &carbohydrate_params = plant_instances.at(plantID).carb_parameters;
401
402 for (const auto &shoot: *shoot_tree) {
403 uint shootID = shoot->ID;
404
405 float shoot_volume = plant_instances.at(plantID).shoot_tree.at(shootID)->calculateShootInternodeVolume();
406
407 float phyllochron_carbon_ratio = shoot->total_carbohydrate_pool_molC / (carbohydrate_params.carbohydrate_phyllochron_threshold * carbohydrate_params.stem_density * shoot_volume / C_molecular_wt);
408
409 shoot->phyllochron_instantaneous = std::fmax(shoot->shoot_parameters.phyllochron_min.val(), shoot->phyllochron_min / (phyllochron_carbon_ratio * dt));
410 }
411 }
412}
413
414void PlantArchitecture::checkCarbonPool_transferCarbon(float dt) {
415 for (auto &[plantID, plant_instance]: plant_instances) {
416 const auto shoot_tree = &plant_instance.shoot_tree;
417 const auto shoot_tree_ptr = &plant_instances.at(plantID).shoot_tree;
418 const CarbohydrateParameters &carbohydrate_params = plant_instances.at(plantID).carb_parameters;
419
420 // Transfer carbon from parent shoots to distal child shoots (gradient-driven flux)
421 for (const auto &shoot_inner: *shoot_tree) {
422 if (!shoot_inner) {
423 continue; // Skip null shoots
424 }
425 uint shootID_inner = shoot_inner->ID;
426 float shoot_volume_inner = plant_instances.at(plantID).shoot_tree.at(shootID_inner)->calculateShootInternodeVolume();
427 if (shoot_volume_inner <= 0.0f)
428 {
429 continue;
430 }
431 float shoot_carb_pool_molC_inner = shoot_inner->sugar_pool_molC;
432 float shoot_carb_conc_inner = shoot_carb_pool_molC_inner / shoot_volume_inner;
433 if (shoot_inner->sugar_pool_molC > carbohydrate_params.carbohydrate_transfer_threshold_up * shoot_volume_inner * carbohydrate_params.stem_density / C_molecular_wt) {
434 float starch_sequestration = carbohydrate_params.starch_sequestration_ratio * shoot_inner->sugar_pool_molC;
435 shoot_inner->sugar_pool_molC -= starch_sequestration;
436 shoot_inner->starch_pool_molC += starch_sequestration;
437 shoot_inner->total_carbohydrate_pool_molC = shoot_inner->sugar_pool_molC + shoot_inner->starch_pool_molC;
438 float totalChildVolume = shoot_inner->sumChildVolume(0);
439 if (totalChildVolume <= 0.0f) {
440 continue;
441 }
442 // Determine carbon pool (mol C) available for transfer from parent shoot.
443 float available_fraction_of_carb =
444 (shoot_inner->sugar_pool_molC - carbohydrate_params.carbohydrate_transfer_threshold_up * shoot_volume_inner * carbohydrate_params.stem_density / C_molecular_wt) / shoot_inner->sugar_pool_molC;
445
446 for (int p = 0; p < shoot_inner->phytomers.size(); p++) {
447 // call recursively for child shoots
448 if (shoot_inner->childIDs.find(p) != shoot_inner->childIDs.end()) {
449 for (int child_shoot_ID: shoot_inner->childIDs.at(p)) {
450 float child_volume = plant_instances.at(plantID).shoot_tree.at(child_shoot_ID)->sumChildVolume(0) + plant_instances.at(plantID).shoot_tree.at(child_shoot_ID)->calculateShootInternodeVolume();
451 float child_ratio = child_volume / totalChildVolume;
452
453 float child_shoot_volume = plant_instances.at(plantID).shoot_tree.at(child_shoot_ID)->calculateShootInternodeVolume();
454
455 if (child_shoot_volume > 0.f) {
456 float child_shoot_carb_pool_molC = shoot_tree_ptr->at(child_shoot_ID)->sugar_pool_molC;
457 float child_shoot_carb_conc = child_shoot_carb_pool_molC / child_shoot_volume;
458 // Only tranfer carbon if the parent shoot has greater carbon concentration than the child shoot.
459 if (shoot_carb_conc_inner > child_shoot_carb_conc) {
460 float transfer_volume = shoot_volume_inner;
461 if (child_shoot_volume < shoot_volume_inner) {
462 transfer_volume = child_shoot_volume;
463 }
464 float delta_C = shoot_carb_conc_inner - child_shoot_carb_conc;
465 float transfer_mol_C_demand = delta_C * child_ratio * transfer_volume * carbohydrate_params.carbon_conductance_up * dt;
466
467 float transfer_mol_C = std::clamp(transfer_mol_C_demand, 0.f, shoot_inner->sugar_pool_molC * available_fraction_of_carb * child_ratio);
468
469 // Mass-balance transfer of carbon between shoots
470 plant_instances.at(plantID).shoot_tree.at(child_shoot_ID)->sugar_pool_molC += transfer_mol_C;
471 shoot_inner->sugar_pool_molC -= transfer_mol_C;
472 }
473 }
474 }
475 }
476 }
477 }
478 }
479
480 // Transfer carbon from distal child shoots to parent shoots (gradient-driven flux)
481 for (const auto &shoot: *shoot_tree) {
482 if (!shoot) {
483 continue; // Skip null shoots
484 }
485 uint shootID = shoot->ID;
486 uint parentID = shoot->parent_shoot_ID;
487
488 float shoot_volume = plant_instances.at(plantID).shoot_tree.at(shootID)->calculateShootInternodeVolume();
489
490 if (shoot_volume <= 0.0f) {
491 continue;
492 }
493
494 float shoot_carb_pool_molC = shoot->sugar_pool_molC;
495 float shoot_carb_conc = shoot_carb_pool_molC / shoot_volume;
496
497 // Only transfer carbon if child shoot has greater carbon concentration than parent
498 if (shoot_carb_pool_molC > carbohydrate_params.carbohydrate_transfer_threshold_down * shoot_volume * carbohydrate_params.stem_density / C_molecular_wt) {
499 float available_fraction_of_carb = (shoot->sugar_pool_molC - carbohydrate_params.carbohydrate_transfer_threshold_down * shoot_volume * carbohydrate_params.stem_density / C_molecular_wt) / shoot->sugar_pool_molC;
500 if (parentID < 10000000) {
501 float parent_shoot_volume = plant_instances.at(plantID).shoot_tree.at(parentID)->calculateShootInternodeVolume();
502
503 float parent_shoot_carb_pool_molC = plant_instances.at(plantID).shoot_tree.at(parentID)->sugar_pool_molC;
504 float parent_shoot_carb_conc = parent_shoot_carb_pool_molC / parent_shoot_volume;
505
506 float delta_C = shoot_carb_conc - parent_shoot_carb_conc;
507 float transfer_mol_C_demand = delta_C * shoot_volume * carbohydrate_params.carbon_conductance_down * dt;
508 // Ensure only available carbon is transferred
509 float transfer_mol_C = std::clamp(transfer_mol_C_demand, 0.f, shoot->sugar_pool_molC * available_fraction_of_carb);
510 // Mass balance transfer of C (mol) from child to parent shoot
511 plant_instances.at(plantID).shoot_tree.at(parentID)->sugar_pool_molC += transfer_mol_C;
512 shoot->sugar_pool_molC -= transfer_mol_C;
513 }
514 }
515 }
516 }
517}
518
519
520void PlantArchitecture::incrementPhytomerInternodeGirth_carb(uint plantID, uint shootID, uint node_number, float dt, bool update_context_geometry) {
521 // Slow Radial growth of shoots if their carbon concentration falls below a sustainable threshold
522 const CarbohydrateParameters &carbohydrate_params = plant_instances.at(plantID).carb_parameters;
523
524 if (plant_instances.find(plantID) == plant_instances.end()) {
525 helios_runtime_error("ERROR (PlantArchitecture::incrementPhytomerInternodeGirth): Plant with ID of " + std::to_string(plantID) + " does not exist.");
526 }
527
528 auto shoot = plant_instances.at(plantID).shoot_tree.at(shootID);
529
530 if (shootID >= plant_instances.at(plantID).shoot_tree.size()) {
531 helios_runtime_error("ERROR (PlantArchitecture::incrementPhytomerInternodeGirth): Shoot with ID of " + std::to_string(shootID) + " does not exist.");
532 } else if (node_number >= shoot->current_node_number) {
533 helios_runtime_error("ERROR (PlantArchitecture::incrementPhytomerInternodeGirth): Cannot scale internode " + std::to_string(node_number) + " because there are only " + std::to_string(shoot->current_node_number) + " nodes in this shoot.");
534 }
535
536 auto phytomer = shoot->phytomers.at(node_number);
537
538 // float leaf_area = phytomer->calculateDownstreamLeafArea();
539 float leaf_area = phytomer->downstream_leaf_area;
540 if (context_ptr->doesObjectExist(shoot->internode_tube_objID)) {
541 context_ptr->setObjectData(shoot->internode_tube_objID, "leaf_area", leaf_area);
542 }
543
544 float phytomer_age = phytomer->age;
545 float girth_area_factor = shoot->shoot_parameters.girth_area_factor.val();
546 if (phytomer_age > 365) {
547 girth_area_factor = shoot->shoot_parameters.girth_area_factor.val() * 365 / phytomer_age;
548 }
549
550
551 float internode_area = girth_area_factor * leaf_area * 1e-4;
552
553 float phytomer_radius = sqrtf(internode_area / PI_F);
554
555 float rho_cw = carbohydrate_params.stem_density * carbohydrate_params.stem_structural_carbon_percentage / C_molecular_wt; // Density of carbon in almond wood (mol C m^-3)
556 float max_shoot_volume = internode_area * shoot->calculateShootLength();
557 float current_shoot_volume = shoot->calculateShootInternodeVolume();
558 float max_carbon_demand = (max_shoot_volume - current_shoot_volume) * rho_cw; //(mol C)
559
560 float threshold_carbon_pool = carbohydrate_params.carbohydrate_growth_threshold * current_shoot_volume * carbohydrate_params.stem_density / C_molecular_wt; //(mol C)
561
562 auto &segment = shoot->shoot_internode_radii.at(node_number);
563 for (float &radius: segment) {
564 if (phytomer_radius > radius) { // radius should only increase
565 float carbon_availability_ratio = std::clamp((shoot->sugar_pool_molC - threshold_carbon_pool) / max_carbon_demand, .05f, 1.f);
566 radius = radius + carbon_availability_ratio * 0.5 * (phytomer_radius - radius);
567 }
568
569
570 if (update_context_geometry && context_ptr->doesObjectExist(shoot->internode_tube_objID)) {
571 context_ptr->setTubeRadii(shoot->internode_tube_objID, flatten(shoot->shoot_internode_radii));
572 }
573 }
574}
575
576
577bool Shoot::sampleVegetativeBudBreak_carb(uint node_index) const {
578 const CarbohydrateParameters &carbohydrate_params = plantarchitecture_ptr->plant_instances.at(plantID).carb_parameters;
579 float shoot_volume = calculateShootInternodeVolume();
580
581 if (node_index >= phytomers.size()) {
582 helios_runtime_error("ERROR (PlantArchitecture::sampleVegetativeBudBreak): Invalid node index. Node index must be less than the number of phytomers on the shoot.");
583 }
584
585 float probability_min = plantarchitecture_ptr->plant_instances.at(this->plantID).shoot_types_snapshot.at(this->shoot_type_label).vegetative_bud_break_probability_min.val();
586 float probability_max = 1.f;
587 float probability_decay = plantarchitecture_ptr->plant_instances.at(this->plantID).shoot_types_snapshot.at(this->shoot_type_label).vegetative_bud_break_probability_decay_rate.val();
588
589 if (total_carbohydrate_pool_molC < carbohydrate_params.carbohydrate_vegetative_break_threshold * shoot_volume * carbohydrate_params.stem_density / C_molecular_wt) {
590 probability_max = total_carbohydrate_pool_molC / (carbohydrate_params.carbohydrate_vegetative_break_threshold * shoot_volume * carbohydrate_params.stem_density / C_molecular_wt);
591 }
592
593 float bud_break_probability;
594 if (!shoot_parameters.growth_requires_dormancy && probability_decay < 0.f) {
595 bud_break_probability = probability_min;
596 } else if (probability_decay > 0.f) { // probability maximum at apex
597 bud_break_probability = std::fmax(probability_min, probability_max - probability_decay * float(this->current_node_number - node_index - 1));
598 } else if (probability_decay < 0.f) { // probability maximum at base
599 bud_break_probability = std::fmax(probability_min, probability_max - fabs(probability_decay) * float(node_index));
600 } else {
601 if (probability_decay == 0.f) {
602 bud_break_probability = probability_min;
603 } else {
604 bud_break_probability = probability_max;
605 }
606 }
607
608 bool bud_break = true;
609 if (context_ptr->randu() > bud_break_probability) {
610 bud_break = false;
611 }
612
613 return bud_break;
614}
615
616
617void Shoot::mobilizeStarch(){
618 if (context_ptr->doesObjectExist(internode_tube_objID)) {
619 sugar_pool_molC += starch_pool_molC;
620 starch_pool_molC = 0.f;
621 }
622}
623
624
626 for (auto &[plantID, plant_instance]: plant_instances) {
627 float Ta_C = Ta - 273.15;
628 const CarbohydrateParameters &carbohydrate_params = plant_instances.at(plantID).carb_parameters;
629 float r_m_r = carbohydrate_params.r_m_r_20 * powf(1.8, (Ta_C - 20) / 10);
630 float r_m_w = carbohydrate_params.r_m_w_20 * powf(1.8, (Ta_C - 20) / 10);
631 plant_instances.at(plantID).stem_maintenance_respiration_rate = r_m_w;
632 plant_instances.at(plantID).root_maintenance_respiration_rate = r_m_r;
633 }
634}