18using namespace helios;
23 nitrogen_model_enabled =
true;
27 nitrogen_model_enabled =
false;
31 return nitrogen_model_enabled;
37 if (plant_instances.find(plantID) == plant_instances.end()) {
38 helios_runtime_error(
"ERROR (PlantArchitecture::setPlantNitrogenParameters): Plant ID " + std::to_string(plantID) +
" does not exist.");
40 plant_instances.at(plantID).nitrogen_parameters = params;
44 for (
uint plantID: plantIDs) {
52 for (
auto &[plantID, plant_instance]: plant_instances) {
59 if (plant_instances.find(plantID) == plant_instances.end()) {
60 helios_runtime_error(
"ERROR (PlantArchitecture::initializePlantNitrogenPools): Plant with ID of " + std::to_string(plantID) +
" does not exist.");
62 if (initial_leaf_N_area < 0) {
63 helios_runtime_error(
"ERROR (PlantArchitecture::initializePlantNitrogenPools): Initial leaf N content per area must be >= 0.");
74 for (
auto &shoot: plant.shoot_tree) {
76 shoot->leaf_nitrogen_gN_m2.clear();
78 for (
auto &phytomer: shoot->phytomers) {
80 for (
uint petiole_idx = 0; petiole_idx < phytomer->leaf_objIDs.size(); petiole_idx++) {
81 for (
uint leaf_idx = 0; leaf_idx < phytomer->leaf_objIDs.at(petiole_idx).size(); leaf_idx++) {
82 uint leaf_objID = phytomer->leaf_objIDs.at(petiole_idx).at(leaf_idx);
92 shoot->leaf_nitrogen_gN_m2[leaf_objID] = initial_leaf_N_area;
95 float total_N_added = initial_leaf_N_area * leaf_area;
106 if (plant_instances.find(plantID) == plant_instances.end()) {
107 helios_runtime_error(
"ERROR (PlantArchitecture::addPlantNitrogen): Plant ID " + std::to_string(plantID) +
" does not exist.");
110 helios_runtime_error(
"ERROR (PlantArchitecture::addPlantNitrogen): Nitrogen amount must be >= 0.");
126 for (
uint plantID: plantIDs) {
133void PlantArchitecture::accumulateLeafNitrogen(
float dt) {
134 for (
auto &[plantID, plant]: plant_instances) {
136 bool has_available_nitrogen = (plant.available_nitrogen_pool_gN > 0);
139 for (
auto &shoot: plant.shoot_tree) {
140 for (
auto &phytomer: shoot->phytomers) {
142 for (
uint petiole_idx = 0; petiole_idx < phytomer->leaf_objIDs.size(); petiole_idx++) {
143 for (
uint leaf_idx = 0; leaf_idx < phytomer->leaf_objIDs.at(petiole_idx).size(); leaf_idx++) {
144 uint leaf_objID = phytomer->leaf_objIDs.at(petiole_idx).at(leaf_idx);
152 float current_N_area = 0;
153 if (shoot->leaf_nitrogen_gN_m2.count(leaf_objID)) {
154 current_N_area = shoot->leaf_nitrogen_gN_m2.at(leaf_objID);
157 shoot->leaf_nitrogen_gN_m2[leaf_objID] = 0;
161 if (!has_available_nitrogen) {
167 if (leaf_area <= 0) {
173 if (N_area_demand <= 0) {
181 float total_N_demand = std::min(N_area_demand, max_N_area_this_step) * leaf_area;
184 float N_to_add = std::min(total_N_demand, plant.available_nitrogen_pool_gN);
187 float N_area_to_add = N_to_add / leaf_area;
188 shoot->leaf_nitrogen_gN_m2[leaf_objID] += N_area_to_add;
191 plant.available_nitrogen_pool_gN -= N_to_add;
193 if (plant.available_nitrogen_pool_gN <= 0) {
194 has_available_nitrogen =
false;
205void PlantArchitecture::remobilizeNitrogen(
float dt) {
206 for (
auto &[plantID, plant]: plant_instances) {
210 float total_N_area = 0;
211 float total_area = 0;
214 for (
auto &shoot: plant.shoot_tree) {
215 for (
auto &[leaf_objID, leaf_N_area]: shoot->leaf_nitrogen_gN_m2) {
220 total_N_area += leaf_N_area * leaf_area;
221 total_area += leaf_area;
226 if (num_leaves == 0 || total_area == 0) {
230 float avg_N_area = total_N_area / total_area;
234 std::vector<std::pair<uint, uint>> source_leaves;
235 std::vector<std::pair<uint, uint>> sink_leaves;
236 std::map<uint, float> source_N_available;
237 std::map<uint, float> sink_N_demand;
238 float total_source_N_available = 0;
239 float total_sink_demand = 0;
242 for (
auto &shoot: plant.shoot_tree) {
243 for (
auto &phytomer: shoot->phytomers) {
245 float leaf_lifespan = plant.max_leaf_lifespan;
246 if (leaf_lifespan <= 0) {
250 float leaf_age = phytomer->age;
251 float age_fraction = leaf_age / leaf_lifespan;
254 for (
uint petiole_idx = 0; petiole_idx < phytomer->leaf_objIDs.size(); petiole_idx++) {
255 for (
uint leaf_idx = 0; leaf_idx < phytomer->leaf_objIDs.at(petiole_idx).size(); leaf_idx++) {
256 uint leaf_objID = phytomer->leaf_objIDs.at(petiole_idx).at(leaf_idx);
262 if (!shoot->leaf_nitrogen_gN_m2.count(leaf_objID)) {
267 float current_N_area = shoot->leaf_nitrogen_gN_m2.at(leaf_objID);
277 if (N_stress_factor < 1.0f) {
278 remobilizable_N_area *= (1.0f + 0.3f * (1.0f - N_stress_factor));
282 float remobilizable_N_total = remobilizable_N_area * leaf_area;
284 if (remobilizable_N_total > 0) {
285 source_leaves.push_back({shoot_idx, leaf_objID});
286 source_N_available[leaf_objID] = remobilizable_N_total;
287 total_source_N_available += remobilizable_N_total;
290 }
else if (age_fraction < 0.5f) {
297 float N_total_demand = N_area_demand * leaf_area;
299 if (N_total_demand > 0) {
300 sink_leaves.push_back({shoot_idx, leaf_objID});
301 sink_N_demand[leaf_objID] = N_total_demand;
302 total_sink_demand += N_total_demand;
312 if (total_source_N_available > 0 && total_sink_demand > 0) {
313 float N_to_remobilize = std::min(total_source_N_available, total_sink_demand);
316 for (
auto &[shoot_idx, source_objID]: source_leaves) {
317 float leaf_contribution = source_N_available[source_objID] / total_source_N_available;
318 float N_removed_total = N_to_remobilize * leaf_contribution;
321 float source_leaf_area = context_ptr->
getObjectArea(source_objID);
322 if (source_leaf_area <= 0) {
325 float N_removed_area = N_removed_total / source_leaf_area;
326 plant.shoot_tree.at(shoot_idx)->leaf_nitrogen_gN_m2[source_objID] -= N_removed_area;
330 for (
auto &[shoot_idx, sink_objID]: sink_leaves) {
331 float leaf_demand_fraction = sink_N_demand[sink_objID] / total_sink_demand;
332 float N_added_total = N_to_remobilize * leaf_demand_fraction;
335 float sink_leaf_area = context_ptr->
getObjectArea(sink_objID);
336 if (sink_leaf_area <= 0) {
339 float N_added_area = N_added_total / sink_leaf_area;
340 plant.shoot_tree.at(shoot_idx)->leaf_nitrogen_gN_m2[sink_objID] += N_added_area;
348void PlantArchitecture::updateNitrogenStressFactor() {
349 for (
auto &[plantID, plant]: plant_instances) {
354 float total_area = 0;
357 for (
auto &shoot: plant.shoot_tree) {
358 for (
auto &phytomer: shoot->phytomers) {
359 for (
uint petiole_idx = 0; petiole_idx < phytomer->leaf_objIDs.size(); petiole_idx++) {
360 for (
uint leaf_idx = 0; leaf_idx < phytomer->leaf_objIDs.at(petiole_idx).size(); leaf_idx++) {
361 uint leaf_objID = phytomer->leaf_objIDs.at(petiole_idx).at(leaf_idx);
367 if (!shoot->leaf_nitrogen_gN_m2.count(leaf_objID)) {
371 float leaf_N_area = shoot->leaf_nitrogen_gN_m2.at(leaf_objID);
375 context_ptr->
setObjectData(leaf_objID,
"leaf_nitrogen_gN_m2", leaf_N_area);
377 total_N += leaf_N_area * leaf_area;
378 total_area += leaf_area;
385 if (num_leaves == 0 || total_area == 0) {
390 float avg_leaf_N_area = total_N / total_area;
396 stress_factor = std::clamp(stress_factor, 0.0f, 1.0f);
400 if (!plant_objIDs.empty()) {
401 context_ptr->
setObjectData(plant_objIDs,
"nitrogen_stress_factor", stress_factor);
408void PlantArchitecture::removeFruitNitrogen() {
409 for (
auto &[plantID, plant]: plant_instances) {
413 for (
auto &shoot: plant.shoot_tree) {
414 for (
auto &phytomer: shoot->phytomers) {
416 for (
auto &petiole: phytomer->floral_buds) {
417 for (
auto &fbud: petiole) {
418 if (fbud.state != BUD_FRUITING) {
423 float scale_increment = fbud.current_fruit_scale_factor - fbud.previous_fruit_scale_factor;
424 if (scale_increment <= 0) {
429 float fruit_area_increment = 0;
430 for (
uint fruit_objID: fbud.inflorescence_objIDs) {
436 float current_fruit_area = context_ptr->
getObjectArea(fruit_objID);
439 float mature_fruit_area = current_fruit_area / (fbud.current_fruit_scale_factor * fbud.current_fruit_scale_factor);
442 float area_increment = mature_fruit_area * (fbud.current_fruit_scale_factor * fbud.current_fruit_scale_factor - fbud.previous_fruit_scale_factor * fbud.previous_fruit_scale_factor);
444 fruit_area_increment += area_increment;
447 if (fruit_area_increment > 0) {
449 float fruit_N_demand = fruit_area_increment * N_params.
fruit_N_area;
452 float N_removed = std::min(fruit_N_demand, plant.available_nitrogen_pool_gN);
453 plant.available_nitrogen_pool_gN -= N_removed;
456 if (plant.available_nitrogen_pool_gN < 0) {
457 plant.available_nitrogen_pool_gN = 0;