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->carbohydrate_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,
"carbohydrate_pool_molC", shoot->carbohydrate_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)->carbohydrate_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 * 2 * 3600.f * 1e-6f;
135 context_ptr->
getPrimitiveData(UUID,
"cumulative_net_photosynthesis", current_net_photo);
137 current_net_photo += new_hourly_photo;
138 context_ptr->
setPrimitiveData(UUID,
"cumulative_net_photosynthesis", current_net_photo);
147void PlantArchitecture::accumulateShootPhotosynthesis()
const {
148 uint A_prim_data_missing = 0;
150 for (
auto &[plantID, plant_instance]: plant_instances) {
152 const auto shoot_tree = &plant_instance.shoot_tree;
154 for (
const auto &shoot: *shoot_tree) {
155 uint shootID = shoot->ID;
156 float net_photosynthesis = 0;
157 float shoot_volume = plant_instances.at(plantID).shoot_tree.at(shoot->ID)->calculateShootInternodeVolume();
159 if (shoot->isdormant) {
160 for (
const auto &phytomer: shoot->phytomers) {
161 for (
const auto &leaf_objID:
flatten(phytomer->leaf_objIDs)) {
162 for (
uint UUID: context_ptr->getObjectPrimitiveUUIDs(leaf_objID)) {
168 for (
const auto &phytomer: shoot->phytomers) {
169 for (
const auto &leaf_objID:
flatten(phytomer->leaf_objIDs)) {
170 for (
uint UUID: context_ptr->getObjectPrimitiveUUIDs(leaf_objID)) {
174 net_photosynthesis += A;
177 A_prim_data_missing++;
184 if (net_photosynthesis >= 0.f) {
185 shoot->carbohydrate_pool_molC += net_photosynthesis;
187 context_ptr->
setObjectData(shoot->internode_tube_objID,
"carbohydrate_concentration", shoot->carbohydrate_pool_molC / shoot_volume);
188 context_ptr->
setObjectData(shoot->internode_tube_objID,
"carbohydrate_pool_molC", shoot->carbohydrate_pool_molC);
189 context_ptr->
setObjectData(shoot->internode_tube_objID,
"daily_net_photosynthesis", net_photosynthesis);
193 if (A_prim_data_missing > 0) {
194 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;
198void PlantArchitecture::subtractShootMaintenanceCarbon(
float dt)
const {
199 for (
const auto &[plantID, plant_instance]: plant_instances) {
200 auto shoot_tree = &plant_instance.shoot_tree;
206 for (
auto &shoot: *shoot_tree) {
208 if (shoot->isdormant && shoot->old_shoot_volume >= 0.f) {
212 context_ptr->
setObjectData(shoot->internode_tube_objID,
"daily_respiration", net_respiration);
213 }
else if (shoot->old_shoot_volume >= 0.f) {
217 context_ptr->
setObjectData(shoot->internode_tube_objID,
"daily_respiration", net_respiration);
224void PlantArchitecture::subtractShootGrowthCarbon() {
225 for (
auto &[plantID, plant_instance]: plant_instances) {
226 const auto shoot_tree = &plant_instance.shoot_tree;
232 for (
const auto &shoot: *shoot_tree) {
233 float shoot_volume = plant_instances.at(plantID).shoot_tree.at(shoot->ID)->calculateShootInternodeVolume();
234 uint parentID = shoot->parent_shoot_ID;
237 float phytomer_shoot_volume = 0;
239 for (
int p = 0; p < shoot->phytomers.size(); p++) {
240 float phytomer_volume = plant_instances.at(plantID).shoot_tree.at(shoot->ID)->phytomers.at(p)->calculatePhytomerVolume(p);
241 float phytomer_age = plant_instances.at(plantID).shoot_tree.at(shoot->ID)->phytomers.at(p)->age;
242 float density_dynamic = phytomer_age / carbohydrate_params.
maturity_age;
244 float rho_cw_dynamic = rho_cw * std::clamp(density_dynamic, carbohydrate_params.
initial_density_ratio, 1.f);
245 float phytomer_growth_carbon_demand = 0.f;
246 if (plant_instances.at(plantID).shoot_tree.at(shoot->ID)->old_shoot_volume > 0.f) {
247 phytomer_growth_carbon_demand = rho_cw_dynamic * (phytomer_volume - plant_instances.at(plantID).shoot_tree.at(shoot->ID)->phytomers.at(p)->old_phytomer_volume);
248 shoot->carbohydrate_pool_molC -= phytomer_growth_carbon_demand ;
249 shoot->carbohydrate_pool_molC -= phytomer_growth_carbon_demand / carbohydrate_params.
shoot_root_ratio;
250 shoot_growth += (phytomer_growth_carbon_demand ) + (phytomer_growth_carbon_demand / carbohydrate_params.
shoot_root_ratio);
252 phytomer_shoot_volume += phytomer_volume;
254 plant_instances.at(plantID).shoot_tree.at(shoot->ID)->phytomers.at(p)->old_phytomer_volume = phytomer_volume;
259 context_ptr->
setObjectData(shoot->internode_tube_objID,
"carbohydrate_concentration", shoot->carbohydrate_pool_molC / shoot_volume);
260 context_ptr->
setObjectData(shoot->internode_tube_objID,
"carbohydrate_pool_molC", shoot->carbohydrate_pool_molC);
261 context_ptr->
setObjectData(shoot->internode_tube_objID,
"daily_growth", shoot_growth);
268float Phytomer::calculateFruitConstructionCosts(
const FloralBud &fbud)
const {
269 const CarbohydrateParameters &carbohydrate_params = plantarchitecture_ptr->plant_instances.at(this->plantID).carb_parameters;
273 float fruit_carbon_cost = 0.f;
276 for (
uint fruit_objID: fbud.inflorescence_objIDs) {
278 fruit_carbon_cost += fruit_construction_cost_base * (mature_volume) * (fbud.current_fruit_scale_factor - fbud.previous_fruit_scale_factor);
281 return fruit_carbon_cost;
284void PlantArchitecture::checkCarbonPool_abortOrgans(
float dt) {
285 for (
auto &[plantID, plant_instance]: plant_instances) {
286 const auto shoot_tree = &plant_instance.shoot_tree;
291 for (
const auto &shoot: *shoot_tree) {
292 uint shootID = shoot->ID;
294 float shoot_volume = plant_instances.at(plantID).shoot_tree.at(shootID)->calculateShootInternodeVolume();
296 float working_carb_pool = shoot->carbohydrate_pool_molC;
298 shoot->days_with_negative_carbon_balance += dt / 2;
300 shoot->days_with_negative_carbon_balance += dt;
305 shoot->days_with_negative_carbon_balance = 0;
309 if (shoot->carbohydrate_pool_molC <= 0.f) {
313 if (shoot->sumShootLeafArea() == 0.f && shoot->sumChildVolume() == 0.f && shoot->isdormant ==
false) {
329 const auto phytomers = &shoot->phytomers;
331 bool living_buds =
true;
332 while (living_buds) {
335 for (
const auto &phytomer: *phytomers) {
336 bool next_phytomer =
false;
338 for (
auto &petiole: phytomer->floral_buds) {
343 bool next_petiole =
false;
344 for (
auto &fbud: petiole) {
348 if (fbud.state == BUD_DORMANT || fbud.state == BUD_DEAD) {
352 for (
uint fruit_objID: fbud.inflorescence_objIDs) {
354 working_carb_pool += fruit_construction_cost_base * (mature_volume) * (fbud.current_fruit_scale_factor - fbud.previous_fruit_scale_factor);
356 phytomer->setFloralBudState(BUD_DEAD, fbud);
367 next_phytomer =
true;
380void PlantArchitecture::checkCarbonPool_adjustPhyllochron(
float dt) {
381 for (
auto &[plantID, plant_instance]: plant_instances) {
382 const auto shoot_tree = &plant_instance.shoot_tree;
385 for (
const auto &shoot: *shoot_tree) {
386 uint shootID = shoot->ID;
388 float shoot_volume = plant_instances.at(plantID).shoot_tree.at(shootID)->calculateShootInternodeVolume();
392 shoot->phyllochron_instantaneous = std::fmax(shoot->shoot_parameters.phyllochron_min.val(), shoot->phyllochron_min / (phyllochron_carbon_ratio * dt));
397void PlantArchitecture::checkCarbonPool_transferCarbon(
float dt) {
398 for (
auto &[plantID, plant_instance]: plant_instances) {
399 const auto shoot_tree = &plant_instance.shoot_tree;
400 const auto shoot_tree_ptr = &plant_instances.at(plantID).shoot_tree;
404 for (
const auto &shoot_inner: *shoot_tree) {
408 uint shootID_inner = shoot_inner->ID;
409 float shoot_volume_inner = plant_instances.at(plantID).shoot_tree.at(shootID_inner)->calculateShootInternodeVolume();
410 float shoot_carb_pool_molC_inner = shoot_inner->carbohydrate_pool_molC;
411 float shoot_carb_conc_inner = shoot_carb_pool_molC_inner / shoot_volume_inner;
412 if (shoot_inner->carbohydrate_pool_molC > carbohydrate_params.carbohydrate_transfer_threshold_up * shoot_volume_inner * carbohydrate_params.
stem_density / C_molecular_wt) {
413 float totalChildVolume = shoot_inner->sumChildVolume(0);
414 if (totalChildVolume <= 0.0f) {
418 float available_fraction_of_carb =
419 (shoot_inner->carbohydrate_pool_molC - carbohydrate_params.carbohydrate_transfer_threshold_up * shoot_volume_inner * carbohydrate_params.
stem_density / C_molecular_wt) / shoot_inner->carbohydrate_pool_molC;
421 for (
int p = 0; p < shoot_inner->phytomers.size(); p++) {
423 if (shoot_inner->childIDs.find(p) != shoot_inner->childIDs.end()) {
424 for (
int child_shoot_ID: shoot_inner->childIDs.at(p)) {
425 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();
426 float child_ratio = child_volume / totalChildVolume;
428 float child_shoot_volume = plant_instances.at(plantID).shoot_tree.at(child_shoot_ID)->calculateShootInternodeVolume();
430 if (child_shoot_volume > 0.f) {
431 float child_shoot_carb_pool_molC = shoot_tree_ptr->at(child_shoot_ID)->carbohydrate_pool_molC;
432 float child_shoot_carb_conc = child_shoot_carb_pool_molC / child_shoot_volume;
434 if (shoot_carb_conc_inner > child_shoot_carb_conc) {
435 float transfer_volume = shoot_volume_inner;
436 if (child_shoot_volume < shoot_volume_inner) {
437 transfer_volume = child_shoot_volume;
439 float delta_C = shoot_carb_conc_inner - child_shoot_carb_conc;
440 float transfer_mol_C_demand = delta_C * child_ratio * transfer_volume * carbohydrate_params.carbon_conductance_up * dt;
442 float transfer_mol_C = std::clamp(transfer_mol_C_demand, 0.f, shoot_inner->carbohydrate_pool_molC * available_fraction_of_carb * child_ratio);
445 plant_instances.at(plantID).shoot_tree.at(child_shoot_ID)->carbohydrate_pool_molC += transfer_mol_C;
446 shoot_inner->carbohydrate_pool_molC -= transfer_mol_C;
456 for (
const auto &shoot: *shoot_tree) {
460 uint shootID = shoot->ID;
461 uint parentID = shoot->parent_shoot_ID;
463 float shoot_volume = plant_instances.at(plantID).shoot_tree.at(shootID)->calculateShootInternodeVolume();
465 if (shoot_volume <= 0.0f) {
469 float shoot_carb_pool_molC = shoot->carbohydrate_pool_molC;
470 float shoot_carb_conc = shoot_carb_pool_molC / shoot_volume;
475 if (parentID < 10000000) {
476 float parent_shoot_volume = plant_instances.at(plantID).shoot_tree.at(parentID)->calculateShootInternodeVolume();
478 float parent_shoot_carb_pool_molC = plant_instances.at(plantID).shoot_tree.at(parentID)->carbohydrate_pool_molC;
479 float parent_shoot_carb_conc = parent_shoot_carb_pool_molC / parent_shoot_volume;
481 float delta_C = shoot_carb_conc - parent_shoot_carb_conc;
482 float transfer_mol_C_demand = delta_C * shoot_volume * carbohydrate_params.carbon_conductance_down * dt;
484 float transfer_mol_C = std::clamp(transfer_mol_C_demand, 0.f, shoot->carbohydrate_pool_molC * available_fraction_of_carb);
486 plant_instances.at(plantID).shoot_tree.at(parentID)->carbohydrate_pool_molC += transfer_mol_C;
487 shoot->carbohydrate_pool_molC -= transfer_mol_C;
495void PlantArchitecture::incrementPhytomerInternodeGirth_carb(
uint plantID,
uint shootID,
uint node_number,
float dt,
bool update_context_geometry) {
499 if (plant_instances.find(plantID) == plant_instances.end()) {
500 helios_runtime_error(
"ERROR (PlantArchitecture::incrementPhytomerInternodeGirth): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
503 auto shoot = plant_instances.at(plantID).shoot_tree.at(shootID);
505 if (shootID >= plant_instances.at(plantID).shoot_tree.size()) {
506 helios_runtime_error(
"ERROR (PlantArchitecture::incrementPhytomerInternodeGirth): Shoot with ID of " + std::to_string(shootID) +
" does not exist.");
507 }
else if (node_number >= shoot->current_node_number) {
508 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.");
511 auto phytomer = shoot->phytomers.at(node_number);
514 float leaf_area = phytomer->downstream_leaf_area;
516 context_ptr->
setObjectData(shoot->internode_tube_objID,
"leaf_area", leaf_area);
519 float phytomer_age = phytomer->age;
520 float girth_area_factor = shoot->shoot_parameters.girth_area_factor.val();
521 if (phytomer_age > 365) {
522 girth_area_factor = shoot->shoot_parameters.girth_area_factor.val() * 365 / phytomer_age;
526 float internode_area = girth_area_factor * leaf_area * 1e-4;
528 float phytomer_radius = sqrtf(internode_area /
PI_F);
531 float max_shoot_volume = internode_area * shoot->calculateShootLength();
532 float current_shoot_volume = shoot->calculateShootInternodeVolume();
533 float max_carbon_demand = (max_shoot_volume - current_shoot_volume) * rho_cw;
537 auto &segment = shoot->shoot_internode_radii.at(node_number);
538 for (
float &radius: segment) {
539 if (phytomer_radius > radius) {
540 float carbon_availability_ratio = std::clamp((shoot->carbohydrate_pool_molC - threshold_carbon_pool) / max_carbon_demand, .05f, 1.f);
541 radius = radius + carbon_availability_ratio * 0.5 * (phytomer_radius - radius);
545 if (update_context_geometry && context_ptr->
doesObjectExist(shoot->internode_tube_objID)) {
546 context_ptr->
setTubeRadii(shoot->internode_tube_objID,
flatten(shoot->shoot_internode_radii));
552bool Shoot::sampleVegetativeBudBreak_carb(
uint node_index)
const {
553 const CarbohydrateParameters &carbohydrate_params = plantarchitecture_ptr->plant_instances.at(plantID).carb_parameters;
556 if (node_index >= phytomers.size()) {
557 helios_runtime_error(
"ERROR (PlantArchitecture::sampleVegetativeBudBreak): Invalid node index. Node index must be less than the number of phytomers on the shoot.");
560 float probability_min = plantarchitecture_ptr->plant_instances.at(this->plantID).shoot_types_snapshot.at(this->shoot_type_label).vegetative_bud_break_probability_min.val();
561 float probability_max = 1.f;
562 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();
568 float bud_break_probability;
570 bud_break_probability = probability_min;
571 }
else if (probability_decay > 0.f) {
572 bud_break_probability = std::fmax(probability_min, probability_max - probability_decay *
float(this->current_node_number - node_index - 1));
573 }
else if (probability_decay < 0.f) {
574 bud_break_probability = std::fmax(probability_min, probability_max - fabs(probability_decay) *
float(node_index));
576 if (probability_decay == 0.f) {
577 bud_break_probability = probability_min;
579 bud_break_probability = probability_max;
583 bool bud_break =
true;
584 if (context_ptr->
randu() > bud_break_probability) {