Overview
The nitrogen model is an optional feature in the PlantArchitecture plugin that simulates nitrogen uptake, allocation, and stress effects on plant growth. When enabled, the model mechanistically tracks nitrogen in a three-level pool structure (root, available, and per-leaf pools), simulates rate-limited nitrogen accumulation in leaves, implements age-based nitrogen remobilization from old to young leaves, and calculates a nitrogen stress factor that other plugins can use to modify photosynthesis and growth rates.
Important architectural note: Nitrogen is tracked on an area basis (g N/m²) rather than a mass basis (g N/g DW). This design choice:
- Eliminates the need for an SLA parameter (simpler model with 7 parameters instead of 8)
- Provides direct compatibility with PROSPECT leaf optical models
- Aligns with physiological reality where leaf N content per area remains relatively constant as leaves thicken (LMA increases) while mass-based concentration decreases
- Matches remote sensing approaches which measure area-based nitrogen
- Simplifies calculations by working directly with leaf area without biomass conversion
The model integrates loosely with other plugins through a single output variable (nitrogen_stress_factor, range 0-1) written to plant object data. The Photosynthesis plugin or other growth models can optionally read this stress factor and use it to scale parameters like Vcmax/Jmax or growth rates.
Key features of the nitrogen model include:
- Area-based nitrogen tracking (g N/m²) for simplicity and PROSPECT compatibility
- Three-level pool structure: root pool → available pool → per-leaf pools
- Rate-limited nitrogen accumulation in leaves (prevents instantaneous uptake)
- Age-based nitrogen remobilization from old leaves (>70% lifespan) to young leaves (<50% lifespan)
- 70% remobilization efficiency with stress-dependent acceleration
- Fruit nitrogen removal during fruit growth
- Single output: nitrogen stress factor (0-1) for use by other plugins
- Only 7 user-configurable parameters
Enabling the Nitrogen Model
The nitrogen model is disabled by default. To enable it, call the PlantArchitecture::enableNitrogenModel() method before or after building plant instances:
plantarchitecture.enableNitrogenModel();
plantarchitecture.loadPlantModelFromLibrary("bean");
uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(nullorigin, 0);
The model can be disabled at any time using PlantArchitecture::disableNitrogenModel(). Once enabled, the model automatically operates during each call to PlantArchitecture::advanceTime().
Model Parameters
The behavior of the nitrogen model is controlled by only 7 parameters stored in the NitrogenParameters structure. Parameters can be set for individual plants or groups of plants using PlantArchitecture::setPlantNitrogenParameters().
Parameter Descriptions
Parameters in the NitrogenParameters structure are organized into several categories. Default values are based on typical crop plants (bean, tomato).
| Category | Parameter | Default Value | Units | Description |
| Leaf Nitrogen Content (Area Basis) |
| target_leaf_N_area | 1.5 | g N/m² | Target leaf nitrogen content per unit area for unstressed plants. Typical range: 1.0-2.5 g N/m². |
| minimum_leaf_N_area | 0.5 | g N/m² | Minimum viable leaf nitrogen content per area below which leaves cannot function. |
| Allocation |
| root_allocation_fraction | 0.15 | - | Fraction of applied nitrogen allocated to root pool (0-1). Remainder goes to available pool. |
| Rate Limiting |
| max_N_accumulation_rate | 0.1 | g N/m²/day | Maximum rate at which leaves can accumulate nitrogen per unit area per day. Prevents instantaneous uptake. |
| Remobilization |
| leaf_remobilization_efficiency | 0.70 | - | Fraction of leaf nitrogen that can be remobilized from old leaves (0-1). Typical range: 0.6-0.8. |
| remobilization_age_threshold | 0.70 | - | Fraction of leaf lifespan at which remobilization begins (0-1). Leaves older than this donate N to younger leaves. |
| Fruit Nitrogen |
| fruit_N_area | 1.0 | g N/m² | Nitrogen content per unit fruit surface area. Used to calculate N demand during fruit growth. |
Setting Parameters
Parameters are set using the PlantArchitecture::setPlantNitrogenParameters() method. Default parameter values are used unless explicitly modified:
plantarchitecture.enableNitrogenModel();
plantarchitecture.loadPlantModelFromLibrary("tomato");
uint plantID = plantarchitecture.buildPlantInstanceFromLibrary(nullorigin, 0);
plantarchitecture.setPlantNitrogenParameters(plantID, nitrogen_params);
You can also set parameters for multiple plants at once:
std::vector<uint> plantIDs = plantarchitecture.buildPlantCanopyFromLibrary(
make_vec3(0,0,0), make_vec2(0.5, 0.5), make_int2(10, 10), 30);
plantarchitecture.setPlantNitrogenParameters(plantIDs, nitrogen_params);
Initialization
After enabling the nitrogen model and setting parameters, you should initialize nitrogen pools based on desired initial leaf nitrogen content per area:
plantarchitecture.initializeNitrogenPools(1.2f);
plantarchitecture.initializePlantNitrogenPools(plantID, 1.2f);
The initialization method:
- Clears existing nitrogen pools (root, available, per-leaf)
- Sets each leaf's nitrogen content to the specified value (g N/m²)
- Calculates total nitrogen by multiplying content × leaf area
- Records the initial nitrogen as cumulative uptake for tracking purposes
Note: Because the model uses area-based nitrogen, initialization does not require knowledge of leaf biomass or SLA. This significantly simplifies the initialization process.
Nitrogen Application
Nitrogen is applied directly to plants using the PlantArchitecture::addPlantNitrogen() method. This immediately updates plant nitrogen pools:
plantarchitecture.addPlantNitrogen(plantID, 0.5f);
plantarchitecture.addPlantNitrogen(plantIDs, 0.5f);
When nitrogen is applied:
- It is immediately split between root pool (15% by default) and available pool (85% by default)
- The split fraction is controlled by
root_allocation_fraction parameter
- Cumulative nitrogen uptake is tracked for each plant
- No scheduling or delayed uptake - application is instantaneous
In a typical simulation, you would apply nitrogen at specific times during the growing season:
for (int day = 100; day <= 250; day++) {
context.setDate(make_Date(day, 2024));
if (day == 115) {
plantarchitecture.addPlantNitrogen(plantIDs, 0.5f);
}
if (day == 145) {
plantarchitecture.addPlantNitrogen(plantIDs, 0.75f);
}
plantarchitecture.advanceTime(1.0f);
}
Nitrogen Dynamics
When PlantArchitecture::advanceTime() is called, the nitrogen model performs several operations in sequence:
1. Leaf Nitrogen Accumulation
Nitrogen from the available pool is transferred to individual leaves in a rate-limited manner:
- Each leaf's current nitrogen content per area (g N/m²) is compared to the target
- Nitrogen demand per area is calculated: demand = target - current (g N/m²)
- Transfer rate is limited by
max_N_accumulation_rate (g N/m²/day)
- Total nitrogen transfer is calculated: (limited demand) × leaf area
- Transfer is also supply-limited by the available pool
- Leaves with nitrogen below target gradually accumulate nitrogen over time
This rate-limiting prevents instantaneous equilibration and creates realistic lag times between nitrogen application and full uptake.
Key simplification: No biomass calculation or SLA parameter needed. The model works directly with leaf areas.
2. Nitrogen Remobilization (Critical Feature)
Nitrogen is redistributed from old leaves to young leaves based on age:
Source leaves (age ≥ 70% of lifespan):
- Classified as nitrogen donors
- Calculate remobilizable nitrogen per area: (current_N_area - minimum_N_area) × 70% efficiency
- Cannot mobilize nitrogen below the minimum content threshold
- Remobilization is accelerated by up to 30% under nitrogen stress
- Total remobilizable N = remobilizable N per area × leaf area
Sink leaves (age < 50% of lifespan):
- Classified as nitrogen receivers
- Young expanding leaves have priority for remobilized nitrogen
- Receive nitrogen in proportion to their demand (target_N_area - current_N_area)
Physiological basis:
- Research shows ~50% of young leaf nitrogen comes from remobilization during normal growth
- Typical remobilization efficiency: 70-80% of leaf nitrogen
- Area-based tracking naturally handles the fact that thicker (higher LMA) older leaves have more total nitrogen to remobilize even if mass-based concentration is similar
- Nitrogen deficiency accelerates senescence by 5-20 days
This remobilization mechanism reproduces the characteristic symptom of nitrogen deficiency where older leaves yellow first while younger leaves remain green.
3. Fruit Nitrogen Removal
When fruit grows (detected by increase in fruit scale factor), nitrogen is deducted from the available pool:
- Fruit area increment is calculated from scale factor changes
- Nitrogen demand = area increment ×
fruit_N_area (g N/m²)
- Per-plant fruit demand is aggregated across all fruiting buds before withdrawal
- Nitrogen is removed from available pool first (supply-limited)
- This reduces nitrogen available for leaves, potentially creating competition
Fruit nitrogen removal is only active when plants are fruiting.
Leaf-to-fruit translocation: when the available pool cannot fully cover fruit demand, the remaining shortfall is satisfied by translocation from leaves. Old leaves (age ≥ remobilization_age_threshold) donate first; if their reserve is exhausted, younger leaves contribute as a fallback. The per-leaf reserve uses the same formula as leaf-to-leaf remobilization — (current_N_area - minimum_leaf_N_area) × leaf_remobilization_efficiency — so the minimum_leaf_N_area floor is respected. Withdrawal is distributed across source leaves proportionally to per-leaf availability. No stress-acceleration multiplier is applied here because translocation magnitude is governed by fruit demand rather than stress signaling. If even leaves cannot cover the demand, the residual shortfall is silently absorbed (mirroring the supply-limited treatment of the available pool).
4. Nitrogen Stress Factor Calculation
After nitrogen dynamics, the model calculates a single output metric:
- Area-weighted average nitrogen content per area is calculated: Σ(N_area × area) / Σ(area)
- Stress factor = min(1.0, avg_N_area / target_N_area)
- Stress factor is clamped to [0, 1] range
- Written to plant object data with label
"nitrogen_stress_factor"
Stress factor interpretation:
- 1.0 = No nitrogen stress (plants at or above target nitrogen)
- 0.5 = Moderate nitrogen stress (50% of target nitrogen)
- 0.0 = Severe nitrogen stress (no nitrogen available)
Integration with Other Plugins
The nitrogen model outputs a single variable that other plugins can optionally read:
Photosynthesis Integration
The Photosynthesis plugin can read the nitrogen stress factor and scale photosynthetic parameters:
float N_stress = 1.0f;
if (context->doesObjectDataExist(objID, "nitrogen_stress_factor")) {
context->getObjectData(objID, "nitrogen_stress_factor", N_stress);
N_stress = std::clamp(N_stress, 0.0f, 1.0f);
}
coeffs.Vcmax *= N_stress;
coeffs.Jmax *= N_stress;
This integration is optional - the Photosynthesis plugin works with or without the nitrogen model.
PROSPECT Leaf Optics Integration
Direct compatibility: The PROSPECT model requires area-based nitrogen (Leaf Nitrogen Density), which is exactly what this model provides.
The nitrogen model writes object data "leaf_nitrogen_gN_m2" (g N/m \(^2\)) for each leaf, and LeafOptics::run() reads that object data directly, so no manual conversion is required:
leafoptics.run( plantarch.getAllLeafUUIDs(), params );
Internally the conversion to the PROSPECT chlorophyll input is \(C_{ab}\,(\mu g/cm^2) = N_{area}\,(g/m^2)\times 100\times f_{photosynthetic}\times k_{N\rightarrow C_{ab}}\).
Why this matters: PROSPECT and other leaf optical models work with area-based properties because reflectance is determined by the amount of absorbing material per unit leaf area, not per unit mass. Using area-based nitrogen eliminates the need for SLA-based conversions that introduce additional uncertainty.
Growth Integration
PlantArchitecture growth calculations can read the stress factor:
float N_stress = 1.0f;
if (context_ptr->doesObjectDataExist(shoot_objID, "nitrogen_stress_factor")) {
context_ptr->getObjectData(shoot_objID, "nitrogen_stress_factor", N_stress);
}
float adjusted_elongation = base_elongation_rate * N_stress;
Complete Usage Example
This example shows a complete simulation with the nitrogen model:
using namespace helios;
int main() {
plantarch.loadPlantModelFromLibrary("bean");
std::vector<uint> plantIDs = plantarch.buildPlantCanopyFromLibrary(
make_vec3(0,0,0), make_vec2(0.3, 0.3), make_int2(10, 10), 30);
plantarch.enableNitrogenModel();
plantarch.setPlantNitrogenParameters(plantIDs, N_params);
plantarch.initializeNitrogenPools(1.2f);
photosynthesis.setFarquharCoefficientsFromLibrary("bean");
for (int day = 100; day <= 250; day++) {
context.
setDate(make_Date(day, 2024));
if (day == 115) {
plantarch.addPlantNitrogen(plantIDs, 0.5f);
}
if (day == 145) {
plantarch.addPlantNitrogen(plantIDs, 0.75f);
}
plantarch.advanceTime(1.0f);
photosynthesis.run();
std::vector<uint> plant_objIDs = plantarch.getAllPlantObjectIDs(plantIDs[0]);
if (!plant_objIDs.empty()) {
float N_stress;
context.
getObjectData(plant_objIDs[0],
"nitrogen_stress_factor", N_stress);
std::cout << "Day " << day << " N stress factor: " << N_stress << std::endl;
}
}
return 0;
}
Design Principles
The nitrogen model follows several key design principles:
1. Simplicity Through Area-Based Tracking
- Only 7 parameters (compared to 8 in mass-based approach)
- No SLA parameter needed (eliminated)
- ~450 lines of code
- No biomass calculations or unit conversions
- Always numerically stable
- Direct compatibility with leaf optical models
2. Physiological Realism
Why area-based nitrogen is more realistic:
Research indicates that as leaves mature and thicken (LMA increases 2-3×), the nitrogen content per area (g N/m²) remains relatively constant, while mass-based concentration (g N/g DW) decreases. This occurs because:
- Leaf thickening is primarily due to structural tissue (cell walls, lignin)
- Photosynthetic machinery (which contains most leaf N) scales with leaf area, not mass
- Rubisco content is determined by light capture, which depends on leaf area
By tracking nitrogen on an area basis, the model:
- Naturally captures the constancy of N_area as leaves thicken
- Eliminates the artificial need to track changing LMA or SLA
- Aligns with how nitrogen actually functions in leaves (per unit light-capturing area)
3. Integration with PlantArchitecture
- Follows the same pattern as CarbohydrateModel
- Part of PlantArchitecture plugin, not a separate plugin
- Shares data structures (PlantInstance, Shoot, Phytomer)
- Integrates seamlessly with advanceTime() loop
4. Loose Coupling
- Single output variable: nitrogen stress factor
- Other plugins decide whether and how to use it
- No direct dependencies on Photosynthesis or other plugins
- Model can be enabled/disabled independently
5. Fail-Fast Philosophy
- Clear error messages using helios_runtime_error()
- No silent fallbacks or default behaviors that hide issues
- Validates inputs (negative amounts, missing plants, etc.)
- Errors are caught immediately with actionable messages
Model Limitations
The nitrogen model is intentionally simplified and has several limitations:
- No soil nitrogen pool: Nitrogen is applied directly to plants, not through a soil model
- No root nitrogen uptake rate: Root pool is passive; uptake is instantaneous
- No nitrogen effects on growth built-in: Growth modifications must be implemented in calling code
- Static LMA assumption: Model assumes N_area is relatively constant; does not track LMA dynamics
- No temperature effects: All rates are constant regardless of temperature
- No nitrogen loss mechanisms: No volatilization, leaching, or denitrification
- Simplified remobilization: Age-based classification; no explicit senescence tracking
These limitations were intentional design choices to keep the model simple and maintainable while capturing the most important nitrogen dynamics.
References
The nitrogen model implementation is based on published research:
- Area-based vs. mass-based nitrogen: Research shows that reflectance spectra are directly sensitive to area-based content rather than mass-based content. The relationship is: N_mass (g/g) = N_area (g/m²) / LMA (g/m²).
- Remobilization efficiency (70%): Masclaux-Daubresse et al. (2010). "Nitrogen uptake, assimilation and remobilization in plants: challenges for sustainable and productive agriculture." Annals of Botany 105(7): 1141-1157.
- Age-based remobilization patterns: Hirel et al. (2007). "The challenge of improving nitrogen use efficiency in crop plants: towards a more central role for genetic variability and quantitative genetics within integrated approaches." Journal of Experimental Botany 58(9): 2369-2387.
- Nitrogen stress effects on photosynthesis: Evans (1989). "Photosynthesis and nitrogen relationships in leaves of C3 plants." Oecologia 78(1): 9-19.
- LMA dynamics and nitrogen: Poorter et al. (2009). "Causes and consequences of variation in leaf mass per area (LMA): a meta-analysis." New Phytologist 182(3): 565-588.
- PROSPECT and area-based nitrogen: N-PROSPECT models use Leaf Nitrogen Density (LND, µg/cm²) as input, which is equivalent to area-based nitrogen content.
Troubleshooting
No Nitrogen Stress Factor Output
Problem: The nitrogen_stress_factor object data does not exist or cannot be read.
Solutions:
Nitrogen Uptake Too Fast/Slow
Problem: Leaves reach target nitrogen too quickly or too slowly after application.
Solutions:
- Adjust
max_N_accumulation_rate parameter (increase for faster, decrease for slower)
- Units are g N/m²/day (note: area-based, not total per day)
- Typical range: 0.05-0.20 g N/m²/day depending on species and growth rate
- Consider the time step (dt) used in advanceTime() - smaller steps = smoother accumulation
No Nitrogen Remobilization
Problem: Old leaves are not donating nitrogen to young leaves.
Solutions:
- Check that plants have leaves of different ages (remobilization requires age gradient)
- Verify
max_leaf_lifespan is set correctly in PlantInstance (not zero or infinite)
- Ensure
remobilization_age_threshold is less than 1.0 (default 0.7)
- Old leaves must have nitrogen above
minimum_leaf_N_area to donate
Parameter Unit Confusion
Problem: Unclear about units or how to convert from literature values.
Solutions:
- All nitrogen parameters use area basis (g N/m²), not mass basis (g N/g DW)
- To convert from mass basis: N_area (g/m²) = N_mass (g/g) × LMA (g/m²)
- Typical crop leaf N_area: 1.0-2.5 g N/m² (equivalent to 2-5% N at LMA = 40-50 g/m²)
- For PROSPECT: multiply by 100 to get µg/cm² (1 g/m² = 100 µg/cm²)