17#include "../include/PlantArchitecture.h"
19using namespace helios;
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;
26 float leaf_construction_cost_base = leaf_carbon_percentage * 0.1 / (C_molecular_wt * SLA);
28 float phytomer_carbon_cost = 0.f;
33 for (
const auto &petiole: leaf_objIDs) {
34 for (
uint leaf_objID: petiole) {
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;
44 phytomer_carbon_cost += leaf_construction_cost_base * leaf_area;
46 return phytomer_carbon_cost;
49float Phytomer::calculateFlowerConstructionCosts(
const FloralBud &fbud)
const {
50 float flower_carbon_cost = 0.f;
52 for (
uint flower_objID: fbud.inflorescence_objIDs) {
53 flower_carbon_cost += plantarchitecture_ptr->plant_instances.at(this->plantID).carb_parameters.total_flower_cost;
56 return flower_carbon_cost;
60 for (
auto &plant: plant_instances) {
61 auto shoot_tree = &plant.second.shoot_tree;
63 for (
auto &shoot: *shoot_tree) {
65 float shoot_volume = shoot->calculateShootInternodeVolume();
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);
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.");
84 for (
auto &shoot: plant_instances.at(plantID).shoot_tree) {
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.");
99 float shoot_volume = plant_instances.at(plantID).shoot_tree.at(shootID)->calculateShootInternodeVolume();
102 plant_instances.at(plantID).shoot_tree.at(shootID)->sugar_pool_molC = shoot_volume * carbohydrate_concentration_molC_m3;
106 for (
const auto &[plantID, plant_instance]: plant_instances) {
107 auto shoot_tree = &plant_instance.shoot_tree;
109 for (
auto &shoot: *shoot_tree) {
111 if (shoot->isdormant) {
112 for (
const auto &phytomer: shoot->phytomers) {
113 for (
const auto &leaf_objID:
flatten(phytomer->leaf_objIDs)) {
120 for (
const auto &phytomer: shoot->phytomers) {
121 for (
auto &leaf_objID:
flatten(phytomer->leaf_objIDs)) {
124 float current_net_photo = 0;
131 float new_hourly_photo = leaf_A * lUUID_area * 3600.f * 1e-6f;
134 context_ptr->
getPrimitiveData(UUID,
"cumulative_net_photosynthesis", current_net_photo);
136 current_net_photo += new_hourly_photo;
137 context_ptr->
setPrimitiveData(UUID,
"cumulative_net_photosynthesis", current_net_photo);
146void PlantArchitecture::accumulateShootPhotosynthesis()
const {
147 uint A_prim_data_missing = 0;
149 for (
auto &[plantID, plant_instance]: plant_instances) {
151 const auto shoot_tree = &plant_instance.shoot_tree;
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();
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)) {
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)) {
173 net_photosynthesis += A;
176 A_prim_data_missing++;
183 if (net_photosynthesis >= 0.f) {
184 shoot->sugar_pool_molC += net_photosynthesis;
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);
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;
199void PlantArchitecture::subtractShootMaintenanceCarbon(
float dt)
const {
200 for (
const auto &[plantID, plant_instance]: plant_instances) {
201 auto shoot_tree = &plant_instance.shoot_tree;
207 for (
auto &shoot: *shoot_tree) {
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;
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;
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);
220void PlantArchitecture::subtractShootGrowthCarbon() {
221 for (
auto &[plantID, plant_instance]: plant_instances) {
222 const auto shoot_tree = &plant_instance.shoot_tree;
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;
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);
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);
244 shoot->sugar_pool_molC -= phytomer_growth_carbon_demand ;
245 shoot->sugar_pool_molC -= phytomer_growth_carbon_demand / carbohydrate_params.
shoot_root_ratio;
246 shoot_growth += (phytomer_growth_carbon_demand ) + (phytomer_growth_carbon_demand / 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))
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);
253 shoot->sugar_pool_molC -= phytomer_growth_carbon_demand ;
254 shoot->sugar_pool_molC -= phytomer_growth_carbon_demand / carbohydrate_params.
shoot_root_ratio;
255 shoot_growth += (phytomer_growth_carbon_demand ) + (phytomer_growth_carbon_demand / carbohydrate_params.
shoot_root_ratio);
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);
269 plant_instances.at(plantID).shoot_tree.at(shoot->ID)->phytomers.at(p)->old_phytomer_volume = phytomer_volume;
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);
286float Phytomer::calculateFruitConstructionCosts(
const FloralBud &fbud)
const {
287 const CarbohydrateParameters &carbohydrate_params = plantarchitecture_ptr->plant_instances.at(this->plantID).carb_parameters;
291 float fruit_carbon_cost = 0.f;
294 for (
uint fruit_objID: fbud.inflorescence_objIDs) {
296 fruit_carbon_cost += fruit_construction_cost_base * (mature_volume) * (fbud.current_fruit_scale_factor - fbud.previous_fruit_scale_factor);
299 return fruit_carbon_cost;
302void PlantArchitecture::checkCarbonPool_abortOrgans(
float dt) {
303 for (
auto &[plantID, plant_instance]: plant_instances) {
304 const auto shoot_tree = &plant_instance.shoot_tree;
309 for (
const auto &shoot: *shoot_tree) {
310 uint shootID = shoot->ID;
312 shoot->total_carbohydrate_pool_molC = shoot->sugar_pool_molC + shoot->starch_pool_molC;
315 if (shoot->total_carbohydrate_pool_molC <= 0.f) {
320 float shoot_volume = plant_instances.at(plantID).shoot_tree.at(shootID)->calculateShootInternodeVolume();
322 float working_carb_pool = shoot->total_carbohydrate_pool_molC;
324 shoot->days_with_negative_carbon_balance += dt / 2;
326 shoot->days_with_negative_carbon_balance += dt;
331 shoot->days_with_negative_carbon_balance = 0;
347 const auto phytomers = &shoot->phytomers;
349 bool living_buds =
true;
350 while (living_buds) {
353 for (
const auto &phytomer: *phytomers) {
354 bool next_phytomer =
false;
356 for (
auto &petiole: phytomer->floral_buds) {
361 bool next_petiole =
false;
362 for (
auto &fbud: petiole) {
366 if (fbud.state == BUD_DORMANT || fbud.state == BUD_DEAD) {
370 for (
uint fruit_objID: fbud.inflorescence_objIDs) {
372 working_carb_pool += fruit_construction_cost_base * (mature_volume) * (fbud.current_fruit_scale_factor - fbud.previous_fruit_scale_factor);
374 phytomer->setFloralBudState(BUD_DEAD, fbud);
385 next_phytomer =
true;
397void PlantArchitecture::checkCarbonPool_adjustPhyllochron(
float dt) {
398 for (
auto &[plantID, plant_instance]: plant_instances) {
399 const auto shoot_tree = &plant_instance.shoot_tree;
402 for (
const auto &shoot: *shoot_tree) {
403 uint shootID = shoot->ID;
405 float shoot_volume = plant_instances.at(plantID).shoot_tree.at(shootID)->calculateShootInternodeVolume();
409 shoot->phyllochron_instantaneous = std::fmax(shoot->shoot_parameters.phyllochron_min.val(), shoot->phyllochron_min / (phyllochron_carbon_ratio * dt));
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;
421 for (
const auto &shoot_inner: *shoot_tree) {
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)
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) {
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) {
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;
446 for (
int p = 0; p < shoot_inner->phytomers.size(); p++) {
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;
453 float child_shoot_volume = plant_instances.at(plantID).shoot_tree.at(child_shoot_ID)->calculateShootInternodeVolume();
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;
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;
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;
467 float transfer_mol_C = std::clamp(transfer_mol_C_demand, 0.f, shoot_inner->sugar_pool_molC * available_fraction_of_carb * child_ratio);
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;
481 for (
const auto &shoot: *shoot_tree) {
485 uint shootID = shoot->ID;
486 uint parentID = shoot->parent_shoot_ID;
488 float shoot_volume = plant_instances.at(plantID).shoot_tree.at(shootID)->calculateShootInternodeVolume();
490 if (shoot_volume <= 0.0f) {
494 float shoot_carb_pool_molC = shoot->sugar_pool_molC;
495 float shoot_carb_conc = shoot_carb_pool_molC / shoot_volume;
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();
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;
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;
509 float transfer_mol_C = std::clamp(transfer_mol_C_demand, 0.f, shoot->sugar_pool_molC * available_fraction_of_carb);
511 plant_instances.at(plantID).shoot_tree.at(parentID)->sugar_pool_molC += transfer_mol_C;
512 shoot->sugar_pool_molC -= transfer_mol_C;
520void PlantArchitecture::incrementPhytomerInternodeGirth_carb(
uint plantID,
uint shootID,
uint node_number,
float dt,
bool update_context_geometry) {
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.");
528 auto shoot = plant_instances.at(plantID).shoot_tree.at(shootID);
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.");
536 auto phytomer = shoot->phytomers.at(node_number);
539 float leaf_area = phytomer->downstream_leaf_area;
541 context_ptr->
setObjectData(shoot->internode_tube_objID,
"leaf_area", leaf_area);
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;
551 float internode_area = girth_area_factor * leaf_area * 1e-4;
553 float phytomer_radius = sqrtf(internode_area /
PI_F);
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;
562 auto &segment = shoot->shoot_internode_radii.at(node_number);
563 for (
float &radius: segment) {
564 if (phytomer_radius > radius) {
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);
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));
577bool Shoot::sampleVegetativeBudBreak_carb(
uint node_index)
const {
578 const CarbohydrateParameters &carbohydrate_params = plantarchitecture_ptr->plant_instances.at(plantID).carb_parameters;
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.");
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();
593 float bud_break_probability;
595 bud_break_probability = probability_min;
596 }
else if (probability_decay > 0.f) {
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) {
599 bud_break_probability = std::fmax(probability_min, probability_max - fabs(probability_decay) *
float(node_index));
601 if (probability_decay == 0.f) {
602 bud_break_probability = probability_min;
604 bud_break_probability = probability_max;
608 bool bud_break =
true;
609 if (context_ptr->
randu() > bud_break_probability) {
617void Shoot::mobilizeStarch(){
619 sugar_pool_molC += starch_pool_molC;
620 starch_pool_molC = 0.f;
626 for (
auto &[plantID, plant_instance]: plant_instances) {
627 float Ta_C = Ta - 273.15;
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;