18using namespace helios;
21float Phytomer::calculatePhytomerConstructionCosts()
const {
22 float leaf_carbon_percentage = plantarchitecture_ptr->plant_instances.at(this->plantID).carb_parameters.leaf_carbon_percentage;
23 float SLA = plantarchitecture_ptr->plant_instances.at(this->plantID).carb_parameters.SLA;
25 float leaf_construction_cost_base = leaf_carbon_percentage / (C_molecular_wt * SLA);
27 float phytomer_carbon_cost = 0.f;
31 for (
const auto &petiole: leaf_objIDs) {
32 for (
uint leaf_objID: petiole) {
36 phytomer_carbon_cost += leaf_construction_cost_base * leaf_area;
38 return phytomer_carbon_cost;
41float Phytomer::calculateFlowerConstructionCosts(
const FloralBud &fbud)
const {
42 float flower_carbon_cost = 0.f;
44 for (
uint flower_objID: fbud.inflorescence_objIDs) {
45 flower_carbon_cost += plantarchitecture_ptr->plant_instances.at(this->plantID).carb_parameters.total_flower_cost;
48 return flower_carbon_cost;
52 for (
auto &plant: plant_instances) {
53 auto shoot_tree = &plant.second.shoot_tree;
55 for (
auto &shoot: *shoot_tree) {
57 float shoot_volume = shoot->calculateShootInternodeVolume();
59 shoot->carbohydrate_pool_molC = shoot_volume * carbohydrate_concentration_molC_m3;
60 context_ptr->
setObjectData(shoot->internode_tube_objID,
"carbohydrate_concentration", carbohydrate_concentration_molC_m3);
68 if (plant_instances.find(plantID) == plant_instances.end()) {
69 helios_runtime_error(
"ERROR (PlantArchitecture::initializePlantCarbohydratePool): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
70 }
else if (carbohydrate_concentration_molC_m3 < 0) {
71 helios_runtime_error(
"ERROR (PlantArchitecture::initializePlantCarbohydratePool): Carbohydrate concentration must be greater than or equal to zero.");
75 for (
auto &shoot: plant_instances.at(plantID).shoot_tree) {
81 if (plant_instances.find(plantID) == plant_instances.end()) {
82 helios_runtime_error(
"ERROR (PlantArchitecture::initializeShootCarbohydratePool): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
83 }
else if (shootID >= plant_instances.at(plantID).shoot_tree.size()) {
84 helios_runtime_error(
"ERROR (PlantArchitecture::initializeShootCarbohydratePool): Shoot with ID of " + std::to_string(shootID) +
" does not exist.");
85 }
else if (carbohydrate_concentration_molC_m3 < 0) {
86 helios_runtime_error(
"ERROR (PlantArchitecture::initializeShootCarbohydratePool): Carbohydrate concentration must be greater than or equal to zero.");
90 float shoot_volume = plant_instances.at(plantID).shoot_tree.at(shootID)->calculateShootInternodeVolume();
93 plant_instances.at(plantID).shoot_tree.at(shootID)->carbohydrate_pool_molC = shoot_volume * carbohydrate_concentration_molC_m3;
97 for (
const auto &[plantID, plant_instance]: plant_instances) {
98 auto shoot_tree = &plant_instance.shoot_tree;
100 for (
auto &shoot: *shoot_tree) {
102 if (shoot->isdormant) {
103 for (
const auto &phytomer: shoot->phytomers) {
104 for (
const auto &leaf_objID:
flatten(phytomer->leaf_objIDs)) {
111 for (
const auto &phytomer: shoot->phytomers) {
112 for (
auto &leaf_objID:
flatten(phytomer->leaf_objIDs)) {
120 float new_hourly_photo = leaf_A * lUUID_area * 3600.f * 1e-6f;
122 float current_net_photo = 0.f;
124 context_ptr->
getPrimitiveData(UUID,
"cumulative_net_photosynthesis", current_net_photo);
126 current_net_photo += new_hourly_photo;
127 context_ptr->
setPrimitiveData(UUID,
"cumulative_net_photosynthesis", current_net_photo);
136void PlantArchitecture::accumulateShootPhotosynthesis()
const {
137 uint A_prim_data_missing = 0;
139 for (
auto &[plantID, plant_instance]: plant_instances) {
141 const auto shoot_tree = &plant_instance.shoot_tree;
143 for (
const auto &shoot: *shoot_tree) {
144 uint shootID = shoot->ID;
145 float net_photosynthesis = 0;
146 float shoot_volume = plant_instances.at(plantID).shoot_tree.at(shoot->ID)->calculateShootInternodeVolume();
148 if (shoot->isdormant) {
149 for (
const auto &phytomer: shoot->phytomers) {
150 for (
const auto &leaf_objID:
flatten(phytomer->leaf_objIDs)) {
151 for (
uint UUID: context_ptr->getObjectPrimitiveUUIDs(leaf_objID)) {
157 for (
const auto &phytomer: shoot->phytomers) {
158 for (
const auto &leaf_objID:
flatten(phytomer->leaf_objIDs)) {
159 for (
uint UUID: context_ptr->getObjectPrimitiveUUIDs(leaf_objID)) {
163 net_photosynthesis += A;
166 A_prim_data_missing++;
173 if (net_photosynthesis >= 0.f) {
174 shoot->carbohydrate_pool_molC += net_photosynthesis;
176 context_ptr->
setObjectData(shoot->internode_tube_objID,
"carbohydrate_concentration", shoot->carbohydrate_pool_molC / shoot_volume);
180 if (A_prim_data_missing > 0) {
181 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;
185void PlantArchitecture::subtractShootMaintenanceCarbon(
float dt)
const {
186 for (
const auto &[plantID, plant_instance]: plant_instances) {
187 auto shoot_tree = &plant_instance.shoot_tree;
193 for (
auto &shoot: *shoot_tree) {
195 if (shoot->isdormant && shoot->old_shoot_volume >= 0.f) {
198 }
else if (shoot->old_shoot_volume >= 0.f) {
207void PlantArchitecture::subtractShootGrowthCarbon() {
208 for (
auto &[plantID, plant_instance]: plant_instances) {
209 const auto shoot_tree = &plant_instance.shoot_tree;
215 for (
const auto &shoot: *shoot_tree) {
216 float shoot_volume = plant_instances.at(plantID).shoot_tree.at(shoot->ID)->calculateShootInternodeVolume();
217 uint parentID = shoot->parent_shoot_ID;
219 for (
int p = 0; p < shoot->phytomers.size(); p++) {
220 float phytomer_volume = plant_instances.at(plantID).shoot_tree.at(shoot->ID)->phytomers.at(p)->calculatePhytomerVolume(p);
221 float phytomer_age = plant_instances.at(plantID).shoot_tree.at(shoot->ID)->phytomers.at(p)->age;
222 float density_dynamic = phytomer_age / carbohydrate_params.
maturity_age;
224 float rho_cw_dynamic = rho_cw * std::clamp(density_dynamic, carbohydrate_params.
initial_density_ratio, 1.f);
225 float phytomer_growth_carbon_demand = 0.f;
226 if (plant_instances.at(plantID).shoot_tree.at(shoot->ID)->old_shoot_volume >= 0.f) {
227 phytomer_growth_carbon_demand = rho_cw_dynamic * (phytomer_volume - plant_instances.at(plantID).shoot_tree.at(shoot->ID)->phytomers.at(p)->old_phytomer_volume);
228 shoot->carbohydrate_pool_molC -= phytomer_growth_carbon_demand;
229 shoot->carbohydrate_pool_molC -= phytomer_growth_carbon_demand / carbohydrate_params.
shoot_root_ratio;
232 plant_instances.at(plantID).shoot_tree.at(shoot->ID)->phytomers.at(p)->old_phytomer_volume = phytomer_volume;
236 context_ptr->
setObjectData(shoot->internode_tube_objID,
"carbohydrate_concentration", shoot->carbohydrate_pool_molC / shoot_volume);
243float Phytomer::calculateFruitConstructionCosts(
const FloralBud &fbud)
const {
244 const CarbohydrateParameters &carbohydrate_params = plantarchitecture_ptr->plant_instances.at(this->plantID).carb_parameters;
248 float fruit_carbon_cost = 0.f;
251 for (
uint fruit_objID: fbud.inflorescence_objIDs) {
253 fruit_carbon_cost += fruit_construction_cost_base * (mature_volume) * (fbud.current_fruit_scale_factor - fbud.previous_fruit_scale_factor);
256 return fruit_carbon_cost;
259void PlantArchitecture::checkCarbonPool_abortOrgans(
float dt) {
260 for (
auto &[plantID, plant_instance]: plant_instances) {
261 const auto shoot_tree = &plant_instance.shoot_tree;
266 for (
const auto &shoot: *shoot_tree) {
267 uint shootID = shoot->ID;
269 float shoot_volume = plant_instances.at(plantID).shoot_tree.at(shootID)->calculateShootInternodeVolume();
271 float working_carb_pool = shoot->carbohydrate_pool_molC;
273 shoot->days_with_negative_carbon_balance += dt / 2;
275 shoot->days_with_negative_carbon_balance += dt;
280 shoot->days_with_negative_carbon_balance = 0;
284 if (shoot->carbohydrate_pool_molC < 0.f) {
288 if (shoot->sumShootLeafArea() == 0.f && shoot->sumChildVolume() == 0.f && shoot->isdormant ==
false) {
304 const auto phytomers = &shoot->phytomers;
306 bool living_buds =
true;
307 while (living_buds) {
310 for (
const auto &phytomer: *phytomers) {
311 bool next_phytomer =
false;
313 for (
auto &petiole: phytomer->floral_buds) {
318 bool next_petiole =
false;
319 for (
auto &fbud: petiole) {
323 if (fbud.state == BUD_DORMANT || fbud.state == BUD_DEAD) {
327 for (
uint fruit_objID: fbud.inflorescence_objIDs) {
329 working_carb_pool += fruit_construction_cost_base * (mature_volume) * (fbud.current_fruit_scale_factor - fbud.previous_fruit_scale_factor);
331 phytomer->setFloralBudState(BUD_DEAD, fbud);
342 next_phytomer =
true;
355void PlantArchitecture::checkCarbonPool_adjustPhyllochron(
float dt) {
356 for (
auto &[plantID, plant_instance]: plant_instances) {
357 const auto shoot_tree = &plant_instance.shoot_tree;
360 for (
const auto &shoot: *shoot_tree) {
361 uint shootID = shoot->ID;
363 float shoot_volume = plant_instances.at(plantID).shoot_tree.at(shootID)->calculateShootInternodeVolume();
367 shoot->phyllochron_instantaneous = std::fmax(shoot->shoot_parameters.phyllochron_min.val(), shoot->phyllochron_min / (phyllochron_carbon_ratio * dt));
372void PlantArchitecture::checkCarbonPool_transferCarbon(
float dt) {
373 for (
auto &[plantID, plant_instance]: plant_instances) {
374 const auto shoot_tree = &plant_instance.shoot_tree;
375 const auto shoot_tree_ptr = &plant_instances.at(plantID).shoot_tree;
379 for (
const auto &shoot_inner: *shoot_tree) {
383 uint shootID_inner = shoot_inner->ID;
384 float shoot_volume_inner = plant_instances.at(plantID).shoot_tree.at(shootID_inner)->calculateShootInternodeVolume();
385 float shoot_carb_pool_molC_inner = shoot_inner->carbohydrate_pool_molC;
386 float shoot_carb_conc_inner = shoot_carb_pool_molC_inner / shoot_volume_inner;
388 float totalChildVolume = shoot_inner->sumChildVolume(0);
389 if (totalChildVolume <= 0.0f) {
393 float available_fraction_of_carb =
396 for (
int p = 0; p < shoot_inner->phytomers.size(); p++) {
398 if (shoot_inner->childIDs.find(p) != shoot_inner->childIDs.end()) {
399 for (
int child_shoot_ID: shoot_inner->childIDs.at(p)) {
400 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();
401 float child_ratio = child_volume / totalChildVolume;
403 float child_shoot_volume = plant_instances.at(plantID).shoot_tree.at(child_shoot_ID)->calculateShootInternodeVolume();
405 if (child_shoot_volume > 0) {
406 float child_shoot_carb_pool_molC = shoot_tree_ptr->at(child_shoot_ID)->carbohydrate_pool_molC;
407 float child_shoot_carb_conc = child_shoot_carb_pool_molC / child_shoot_volume;
409 if (shoot_carb_conc_inner > child_shoot_carb_conc) {
410 float transfer_volume = shoot_volume_inner;
411 if (child_shoot_volume < shoot_volume_inner) {
412 transfer_volume = child_shoot_volume;
414 float delta_C = shoot_carb_conc_inner - child_shoot_carb_conc;
415 float transfer_mol_C_demand = delta_C * child_ratio * transfer_volume * carbohydrate_params.carbon_conductance_up * dt;
417 float transfer_mol_C = std::clamp(transfer_mol_C_demand, 0.f, shoot_inner->carbohydrate_pool_molC * available_fraction_of_carb * child_ratio);
420 plant_instances.at(plantID).shoot_tree.at(child_shoot_ID)->carbohydrate_pool_molC += transfer_mol_C;
421 shoot_inner->carbohydrate_pool_molC -= transfer_mol_C;
431 for (
const auto &shoot: *shoot_tree) {
435 uint shootID = shoot->ID;
436 uint parentID = shoot->parent_shoot_ID;
438 float shoot_volume = plant_instances.at(plantID).shoot_tree.at(shootID)->calculateShootInternodeVolume();
440 if (shoot_volume <= 0.0f) {
444 float shoot_carb_pool_molC = shoot->carbohydrate_pool_molC;
445 float shoot_carb_conc = shoot_carb_pool_molC / shoot_volume;
449 float available_fraction_of_carb = (shoot->carbohydrate_pool_molC - carbohydrate_params.
carbohydrate_transfer_threshold * shoot_volume * carbohydrate_params.
stem_density / C_molecular_wt) / shoot->carbohydrate_pool_molC;
450 if (parentID < 10000000) {
451 float parent_shoot_volume = plant_instances.at(plantID).shoot_tree.at(parentID)->calculateShootInternodeVolume();
453 float parent_shoot_carb_pool_molC = plant_instances.at(plantID).shoot_tree.at(parentID)->carbohydrate_pool_molC;
454 float parent_shoot_carb_conc = parent_shoot_carb_pool_molC / parent_shoot_volume;
456 float delta_C = shoot_carb_conc - parent_shoot_carb_conc;
457 float transfer_mol_C_demand = delta_C * shoot_volume * carbohydrate_params.carbon_conductance_down * dt;
459 float transfer_mol_C = std::clamp(transfer_mol_C_demand, 0.f, shoot->carbohydrate_pool_molC * available_fraction_of_carb);
461 plant_instances.at(plantID).shoot_tree.at(parentID)->carbohydrate_pool_molC += transfer_mol_C;
462 shoot->carbohydrate_pool_molC -= transfer_mol_C;
470void PlantArchitecture::incrementPhytomerInternodeGirth_carb(
uint plantID,
uint shootID,
uint node_number,
float dt,
bool update_context_geometry) {
474 if (plant_instances.find(plantID) == plant_instances.end()) {
475 helios_runtime_error(
"ERROR (PlantArchitecture::incrementPhytomerInternodeGirth): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
478 auto shoot = plant_instances.at(plantID).shoot_tree.at(shootID);
480 if (shootID >= plant_instances.at(plantID).shoot_tree.size()) {
481 helios_runtime_error(
"ERROR (PlantArchitecture::incrementPhytomerInternodeGirth): Shoot with ID of " + std::to_string(shootID) +
" does not exist.");
482 }
else if (node_number >= shoot->current_node_number) {
483 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.");
486 auto phytomer = shoot->phytomers.at(node_number);
489 float leaf_area = phytomer->downstream_leaf_area;
491 context_ptr->
setObjectData(shoot->internode_tube_objID,
"leaf_area", leaf_area);
494 float phytomer_age = phytomer->age;
495 float girth_area_factor = shoot->shoot_parameters.girth_area_factor.val();
496 if (phytomer_age > 365) {
497 girth_area_factor = shoot->shoot_parameters.girth_area_factor.val() * 365 / phytomer_age;
501 float internode_area = girth_area_factor * leaf_area * 1e-4;
502 phytomer->parent_shoot_ptr->shoot_parameters.girth_area_factor.resample();
504 float phytomer_radius = sqrtf(internode_area /
PI_F);
507 float max_shoot_volume = internode_area * shoot->calculateShootLength();
508 float current_shoot_volume = shoot->calculateShootInternodeVolume();
509 float max_carbon_demand = (max_shoot_volume - current_shoot_volume) * rho_cw;
513 auto &segment = shoot->shoot_internode_radii.at(node_number);
514 for (
float &radius: segment) {
515 if (phytomer_radius > radius) {
516 float carbon_availability_ratio = std::clamp((shoot->carbohydrate_pool_molC - threshold_carbon_pool) / max_carbon_demand, .05f, 1.f);
517 radius = radius + carbon_availability_ratio * 0.5 * (phytomer_radius - radius);
521 if (update_context_geometry && context_ptr->
doesObjectExist(shoot->internode_tube_objID)) {
522 context_ptr->
setTubeRadii(shoot->internode_tube_objID,
flatten(shoot->shoot_internode_radii));
528bool Shoot::sampleVegetativeBudBreak_carb(
uint node_index)
const {
529 const CarbohydrateParameters &carbohydrate_params = plantarchitecture_ptr->plant_instances.at(plantID).carb_parameters;
532 if (node_index >= phytomers.size()) {
533 helios_runtime_error(
"ERROR (PlantArchitecture::sampleVegetativeBudBreak): Invalid node index. Node index must be less than the number of phytomers on the shoot.");
536 float probability_min = plantarchitecture_ptr->shoot_types.at(this->shoot_type_label).vegetative_bud_break_probability_min.val();
537 float probability_max = 1.f;
538 float probability_decay = plantarchitecture_ptr->shoot_types.at(this->shoot_type_label).vegetative_bud_break_probability_decay_rate.val();
544 float bud_break_probability;
546 bud_break_probability = probability_min;
547 }
else if (probability_decay > 0) {
548 bud_break_probability = std::fmax(probability_min, probability_max - probability_decay *
float(this->current_node_number - node_index - 1));
549 }
else if (probability_decay < 0) {
550 bud_break_probability = std::fmax(probability_min, probability_max - fabs(probability_decay) *
float(node_index));
552 if (probability_decay == 0.f) {
553 bud_break_probability = probability_min;
555 bud_break_probability = probability_max;
559 bool bud_break =
true;
560 if (context_ptr->
randu() > bud_break_probability) {