0.1.8
Loading...
Searching...
No Matches
photosynthesis.py
Go to the documentation of this file.
1"""
2Photosynthesis parameter structures and data classes for PyHelios.
3
4This module provides Python data structures that mirror the C++ parameter
5classes used by the PhotosynthesisModel plugin, with proper defaults and
6validation support.
7"""
8
9from dataclasses import dataclass, field
10from typing import List, Optional, Union
11import math
12
13# Known species in the photosynthesis library (21 species with aliases)
14PHOTOSYNTHESIS_SPECIES = [
15 "Almond", "Apple", "Cherry", "Prune", "Pear",
16 "PistachioFemale", "PistachioMale", "Walnut",
17 "Grape", # cv. Cabernet Sauvignon
18 "Elderberry", "Toyon", "Big_Leaf_Maple", "Western_Redbud", "Baylaurel", "Olive",
19 "EasternRedbudSunlit", "EasternRedbudShaded"
20]
21
22# Species aliases for case-insensitive and format-flexible lookup
23SPECIES_ALIASES = {
24 # Standard names (already in PHOTOSYNTHESIS_SPECIES)
25 "almond": "Almond",
26 "apple": "Apple",
27 "cherry": "Cherry",
28 "prune": "Prune",
29 "pear": "Pear",
30 "pistachiofemale": "PistachioFemale",
31 "pistachiomale": "PistachioMale",
32 "walnut": "Walnut",
33 "grape": "Grape",
34 "elderberry": "Elderberry",
35 "toyon": "Toyon",
36 "big_leaf_maple": "Big_Leaf_Maple",
37 "western_redbud": "Western_Redbud",
38 "baylaurel": "Baylaurel",
39 "olive": "Olive",
40 "easternredbudsunlit": "EasternRedbudSunlit",
41 "easternredbudshaded": "EasternRedbudShaded",
42
43 # Common aliases and variations
44 "bigleafmaple": "Big_Leaf_Maple",
45 "bigmaple": "Big_Leaf_Maple",
46 "westernredbud": "Western_Redbud",
47 "redbud": "Western_Redbud",
48 "easternredbud": "EasternRedbudSunlit", # Default to sunlit
49 "pistachio": "PistachioFemale", # Default to female
50 "cabernet": "Grape",
51 "cabernetSauvignon": "Grape",
52 "grapevine": "Grape"
53}
54
55
56@dataclass
58 """
59 Temperature response parameters for photosynthetic processes.
60
61 These parameters define how photosynthetic rates vary with temperature
62 using the modified Arrhenius equation.
63
64 Attributes:
65 value_at_25C: Value of the parameter at 25°C
66 dHa: Activation energy (rate of increase parameter)
67 dHd: Deactivation energy (rate of decrease parameter)
68 Topt: Optimum temperature in Kelvin (10000K means no optimum)
69 """
70 value_at_25C: float = 100.0
71 dHa: float = 60.0
72 dHd: float = 600.0
73 Topt: float = 10000.0 # Very high = no temperature optimum
74
75 def __post_init__(self):
76 """Validate parameter values after initialization."""
77 if not math.isfinite(self.value_at_25C):
78 raise ValueError("value_at_25C must be finite")
79 if not math.isfinite(self.dHa) or self.dHa < 0:
80 raise ValueError("dHa must be finite and non-negative")
81 if not math.isfinite(self.dHd) or self.dHd < 0:
82 raise ValueError("dHd must be finite and non-negative")
83 if not math.isfinite(self.Topt) or self.Topt < 0:
84 raise ValueError("Topt must be finite and non-negative")
85
86
87@dataclass
89 """
90 Empirical photosynthesis model coefficients.
91
92 This model uses empirical relationships to estimate photosynthetic
93 rates based on environmental conditions.
94
95 Attributes:
96 Tref: Reference temperature (K)
97 Ci_ref: Reference CO2 concentration (μmol CO2/mol air)
98 Asat: Light-saturated photosynthetic rate (μmol/m²/s)
99 theta: Half-saturation light level (W/m²)
100 Tmin: Minimum temperature for photosynthesis (K)
101 Topt: Optimum temperature for photosynthesis (K)
102 q: Temperature response parameter (unitless)
103 R: Respiration temperature coefficient (μmol·K^0.5/m²/s)
104 ER: Respiration activation energy (1/K)
105 kC: CO2 response coefficient (unitless)
106 """
107 Tref: float = 298.0 # K
108 Ci_ref: float = 290.0 # μmol CO2/mol air
109 Asat: float = 18.18 # μmol/m²/s
110 theta: float = 62.03 # W/m²
111 Tmin: float = 290.0 # K
112 Topt: float = 303.0 # K
113 q: float = 0.344 # unitless
114 R: float = 1.663e5 # μmol·K^0.5/m²/s
115 ER: float = 3740.0 # 1/K
116 kC: float = 0.791 # unitless
117
118 def __post_init__(self):
119 """Validate parameter values after initialization."""
120 if self.Tref <= 0:
121 raise ValueError("Reference temperature must be positive")
122 if self.Ci_ref <= 0:
123 raise ValueError("Reference CO2 concentration must be positive")
124 if self.Asat < 0:
125 raise ValueError("Light-saturated photosynthetic rate cannot be negative")
126 if self.theta <= 0:
127 raise ValueError("Half-saturation light level must be positive")
128 if self.Tmin <= 0:
129 raise ValueError("Minimum temperature must be positive")
130 if self.Topt <= 0:
131 raise ValueError("Optimum temperature must be positive")
132 if self.Tmin >= self.Topt:
133 raise ValueError("Minimum temperature must be less than optimum temperature")
134 if self.q <= 0:
135 raise ValueError("Temperature response parameter must be positive")
136 if self.R < 0:
137 raise ValueError("Respiration coefficient cannot be negative")
138 if self.ER < 0:
139 raise ValueError("Respiration activation energy cannot be negative")
140 if self.kC < 0:
141 raise ValueError("CO2 response coefficient cannot be negative")
143 def to_array(self) -> List[float]:
144 """Convert to float array for C++ interface."""
145 return [
146 self.Tref, self.Ci_ref, self.Asat, self.theta, self.Tmin,
147 self.Topt, self.q, self.R, self.ER, self.kC
148 ]
149
150 @classmethod
151 def from_array(cls, coefficients: List[float]) -> 'EmpiricalModelCoefficients':
152 """Create from float array (from C++ interface)."""
153 if len(coefficients) < 10:
154 raise ValueError("Need at least 10 coefficients for empirical model")
155 return cls(
156 Tref=coefficients[0], Ci_ref=coefficients[1], Asat=coefficients[2],
157 theta=coefficients[3], Tmin=coefficients[4], Topt=coefficients[5],
158 q=coefficients[6], R=coefficients[7], ER=coefficients[8], kC=coefficients[9]
159 )
160
161
162@dataclass
164 """
165 Farquhar-von Caemmerer-Berry photosynthesis model coefficients.
166
167 This model provides a mechanistic description of leaf photosynthesis
168 based on biochemical limitations and temperature responses.
169
170 Core Parameters (at 25°C):
171 Vcmax: Maximum carboxylation rate (μmol/m²/s, -1 = uninitialized)
172 Jmax: Maximum electron transport rate (μmol/m²/s, -1 = uninitialized)
173 alpha: Quantum efficiency of photosystem II (μmol electrons/μmol photons)
174 Rd: Dark respiration rate (μmol/m²/s, -1 = uninitialized)
175 O: Ambient oxygen concentration (mmol/mol)
176 TPU_flag: Enable triose phosphate utilization limitation (0/1)
177
178 Temperature Response Parameters:
179 c_*: Scaling factor for Arrhenius equation
180 dH_*: Activation energy for temperature response
181 """
182 # Core parameters at 25°C
183 Vcmax: float = -1.0 # μmol/m²/s (uninitialized)
184 Jmax: float = -1.0 # μmol/m²/s (uninitialized)
185 alpha: float = -1.0 # unitless (uninitialized)
186 Rd: float = -1.0 # μmol/m²/s (uninitialized)
187 O: float = 213.5 # ambient oxygen concentration (mmol/mol)
188 TPU_flag: int = 0 # enable TPU limitation
189
190 # Temperature scaling factors (c_*)
191 c_Rd: float = 18.72
192 c_Vcmax: float = 26.35
193 c_Jmax: float = 18.86
194 c_Gamma: float = 19.02
195 c_Kc: float = 38.05
196 c_Ko: float = 20.30
197
198 # Activation energies (dH_*)
199 dH_Rd: float = 46.39
200 dH_Vcmax: float = 65.33
201 dH_Jmax: float = 46.36
202 dH_Gamma: float = 37.83
203 dH_Kc: float = 79.43
204 dH_Ko: float = 36.38
205
206 # Temperature response parameter containers
207 _vcmax_temp_response: Optional[PhotosyntheticTemperatureResponseParameters] = field(default=None, init=False)
208 _jmax_temp_response: Optional[PhotosyntheticTemperatureResponseParameters] = field(default=None, init=False)
209 _rd_temp_response: Optional[PhotosyntheticTemperatureResponseParameters] = field(default=None, init=False)
210 _alpha_temp_response: Optional[PhotosyntheticTemperatureResponseParameters] = field(default=None, init=False)
211 _theta_temp_response: Optional[PhotosyntheticTemperatureResponseParameters] = field(default=None, init=False)
213 def __post_init__(self):
214 """Validate parameter values after initialization."""
215 if self.O <= 0:
216 raise ValueError("Oxygen concentration must be positive")
217 if self.TPU_flag not in (0, 1):
218 raise ValueError("TPU_flag must be 0 or 1")
220 # Validate temperature parameters
221 for param_name, value in [
222 ('c_Rd', self.c_Rd), ('c_Vcmax', self.c_Vcmax), ('c_Jmax', self.c_Jmax),
223 ('c_Gamma', self.c_Gamma), ('c_Kc', self.c_Kc), ('c_Ko', self.c_Ko),
224 ('dH_Rd', self.dH_Rd), ('dH_Vcmax', self.dH_Vcmax), ('dH_Jmax', self.dH_Jmax),
225 ('dH_Gamma', self.dH_Gamma), ('dH_Kc', self.dH_Kc), ('dH_Ko', self.dH_Ko)
226 ]:
227 if not math.isfinite(value):
228 raise ValueError(f"Temperature parameter {param_name} must be finite")
230 def setVcmax(self, vcmax_at_25c: float, dha: Optional[float] = None,
231 topt: Optional[float] = None, dhd: Optional[float] = None) -> None:
232 """Set Vcmax with temperature response (mimics C++ overloads)."""
233 if dha is None:
234 # 1-parameter version
236 elif topt is None:
237 # 2-parameter version
239 elif dhd is None:
240 # 3-parameter version
242 else:
243 # 4-parameter version
244 self._vcmax_temp_response = PhotosyntheticTemperatureResponseParameters(vcmax_at_25c, dha, dhd, topt)
245
246 self.Vcmax = vcmax_at_25c
247
248 def setJmax(self, jmax_at_25c: float, dha: Optional[float] = None,
249 topt: Optional[float] = None, dhd: Optional[float] = None) -> None:
250 """Set Jmax with temperature response (mimics C++ overloads)."""
251 if dha is None:
253 elif topt is None:
255 elif dhd is None:
257 else:
258 self._jmax_temp_response = PhotosyntheticTemperatureResponseParameters(jmax_at_25c, dha, dhd, topt)
260 self.Jmax = jmax_at_25c
261
262 def setRd(self, rd_at_25c: float, dha: Optional[float] = None,
263 topt: Optional[float] = None, dhd: Optional[float] = None) -> None:
264 """Set dark respiration with temperature response (mimics C++ overloads)."""
265 if dha is None:
267 elif topt is None:
269 elif dhd is None:
271 else:
272 self._rd_temp_response = PhotosyntheticTemperatureResponseParameters(rd_at_25c, dha, dhd, topt)
273
274 self.Rd = rd_at_25c
275
276 def setQuantumEfficiency_alpha(self, alpha_at_25c: float, dha: Optional[float] = None,
277 topt: Optional[float] = None, dhd: Optional[float] = None) -> None:
278 """Set quantum efficiency with temperature response (mimics C++ overloads)."""
279 if dha is None:
281 elif topt is None:
283 elif dhd is None:
285 else:
286 self._alpha_temp_response = PhotosyntheticTemperatureResponseParameters(alpha_at_25c, dha, dhd, topt)
287
288 self.alpha = alpha_at_25c
289
290 def setLightResponseCurvature_theta(self, theta_at_25c: float, dha: Optional[float] = None,
291 topt: Optional[float] = None, dhd: Optional[float] = None) -> None:
292 """Set light response curvature with temperature response (mimics C++ overloads)."""
293 if dha is None:
295 elif topt is None:
297 elif dhd is None:
299 else:
300 self._theta_temp_response = PhotosyntheticTemperatureResponseParameters(theta_at_25c, dha, dhd, topt)
301
302 def getVcmaxTempResponse(self) -> PhotosyntheticTemperatureResponseParameters:
303 """Get Vcmax temperature response parameters."""
304 if self._vcmax_temp_response is None:
305 return PhotosyntheticTemperatureResponseParameters(self.Vcmax if self.Vcmax > 0 else 100.0)
306 return self._vcmax_temp_response
307
308 def getJmaxTempResponse(self) -> PhotosyntheticTemperatureResponseParameters:
309 """Get Jmax temperature response parameters."""
310 if self._jmax_temp_response is None:
311 return PhotosyntheticTemperatureResponseParameters(self.Jmax if self.Jmax > 0 else 100.0)
312 return self._jmax_temp_response
313
314 def getRdTempResponse(self) -> PhotosyntheticTemperatureResponseParameters:
315 """Get dark respiration temperature response parameters."""
316 if self._rd_temp_response is None:
317 return PhotosyntheticTemperatureResponseParameters(self.Rd if self.Rd > 0 else 1.0)
318 return self._rd_temp_response
320 def getQuantumEfficiencyTempResponse(self) -> PhotosyntheticTemperatureResponseParameters:
321 """Get quantum efficiency temperature response parameters."""
322 if self._alpha_temp_response is None:
323 return PhotosyntheticTemperatureResponseParameters(self.alpha if self.alpha > 0 else 0.3)
324 return self._alpha_temp_response
325
326 def getLightResponseCurvatureTempResponse(self) -> PhotosyntheticTemperatureResponseParameters:
327 """Get light response curvature temperature response parameters."""
328 if self._theta_temp_response is None:
330 return self._theta_temp_response
332 def to_array(self) -> List[float]:
333 """Convert to float array for C++ interface."""
334 return [
335 self.Vcmax, self.Jmax, self.alpha, self.Rd, self.O, float(self.TPU_flag),
336 # Temperature scaling factors
337 self.c_Vcmax, self.dH_Vcmax, self.c_Jmax, self.dH_Jmax,
338 self.c_Rd, self.dH_Rd, self.c_Kc, self.dH_Kc,
339 self.c_Ko, self.dH_Ko, self.c_Gamma, self.dH_Gamma
340 ]
341
342 @classmethod
343 def from_array(cls, coefficients: List[float]) -> 'FarquharModelCoefficients':
344 """Create from float array (from C++ interface)."""
345 if len(coefficients) < 18:
346 raise ValueError("Need at least 18 coefficients for Farquhar model")
347
348 return cls(
349 Vcmax=coefficients[0], Jmax=coefficients[1], alpha=coefficients[2],
350 Rd=coefficients[3], O=coefficients[4], TPU_flag=int(coefficients[5]),
351 c_Vcmax=coefficients[6], dH_Vcmax=coefficients[7],
352 c_Jmax=coefficients[8], dH_Jmax=coefficients[9],
353 c_Rd=coefficients[10], dH_Rd=coefficients[11],
354 c_Kc=coefficients[12], dH_Kc=coefficients[13],
355 c_Ko=coefficients[14], dH_Ko=coefficients[15],
356 c_Gamma=coefficients[16], dH_Gamma=coefficients[17]
357 )
358
359
360def validate_species_name(species: str) -> str:
361 """
362 Validate and normalize species name for photosynthesis library.
363
364 Args:
365 species: Species name (case insensitive, supports aliases)
366
367 Returns:
368 Normalized species name
369
370 Raises:
371 ValueError: If species is not recognized
372 """
373 if not species:
374 raise ValueError("Species name cannot be empty")
375
376 # Try exact match first (case sensitive)
377 if species in PHOTOSYNTHESIS_SPECIES:
378 return species
379
380 # Try case-insensitive match
381 species_lower = species.lower()
382 if species_lower in SPECIES_ALIASES:
383 return SPECIES_ALIASES[species_lower]
384
385 # Try case-insensitive match against known species
386 for known_species in PHOTOSYNTHESIS_SPECIES:
387 if known_species.lower() == species_lower:
388 return known_species
389
390 # Species not found - provide helpful error message
391 available_species = sorted(set(list(PHOTOSYNTHESIS_SPECIES) + list(SPECIES_ALIASES.keys())))
392 raise ValueError(
393 f"Unknown species '{species}'. Available species and aliases:\n"
394 f" {', '.join(available_species[:8])}\n"
395 f" {', '.join(available_species[8:16])}\n"
396 f" {', '.join(available_species[16:])}"
397 )
398
399
400def get_available_species() -> List[str]:
401 """Get list of available species in the photosynthesis library."""
402 return sorted(PHOTOSYNTHESIS_SPECIES.copy())
403
404
405def get_species_aliases() -> dict:
406 """Get dictionary of species aliases."""
407 return SPECIES_ALIASES.copy()
Empirical photosynthesis model coefficients.
float Topt
Optimum temperature for photosynthesis (K)
float Tmin
Minimum temperature for photosynthesis (K)
float Asat
Light-saturated photosynthetic rate (μmol/m²/s)
__post_init__(self)
Validate parameter values after initialization.
float q
Temperature response parameter (unitless)
'EmpiricalModelCoefficients' from_array(cls, List[float] coefficients)
Create from float array (from C++ interface).
float R
Respiration temperature coefficient (μmol·K^0.5/m²/s)
List[float] to_array(self)
Convert to float array for C++ interface.
float ER
Respiration activation energy (1/K)
float Ci_ref
Reference CO2 concentration (μmol CO2/mol air)
float theta
Half-saturation light level (W/m²)
float kC
CO2 response coefficient (unitless)
Farquhar-von Caemmerer-Berry photosynthesis model coefficients.
List[float] to_array(self)
Convert to float array for C++ interface.
None setVcmax(self, float vcmax_at_25c, Optional[float] dha=None, Optional[float] topt=None, Optional[float] dhd=None)
Set Vcmax with temperature response (mimics C++ overloads).
__post_init__(self)
Validate parameter values after initialization.
PhotosyntheticTemperatureResponseParameters getLightResponseCurvatureTempResponse(self)
Get light response curvature temperature response parameters.
None setQuantumEfficiency_alpha(self, float alpha_at_25c, Optional[float] dha=None, Optional[float] topt=None, Optional[float] dhd=None)
Set quantum efficiency with temperature response (mimics C++ overloads).
PhotosyntheticTemperatureResponseParameters getQuantumEfficiencyTempResponse(self)
Get quantum efficiency temperature response parameters.
PhotosyntheticTemperatureResponseParameters getVcmaxTempResponse(self)
Get Vcmax temperature response parameters.
'FarquharModelCoefficients' from_array(cls, List[float] coefficients)
Create from float array (from C++ interface).
PhotosyntheticTemperatureResponseParameters getRdTempResponse(self)
Get dark respiration temperature response parameters.
PhotosyntheticTemperatureResponseParameters getJmaxTempResponse(self)
Get Jmax temperature response parameters.
None setLightResponseCurvature_theta(self, float theta_at_25c, Optional[float] dha=None, Optional[float] topt=None, Optional[float] dhd=None)
Set light response curvature with temperature response (mimics C++ overloads).
None setRd(self, float rd_at_25c, Optional[float] dha=None, Optional[float] topt=None, Optional[float] dhd=None)
Set dark respiration with temperature response (mimics C++ overloads).
None setJmax(self, float jmax_at_25c, Optional[float] dha=None, Optional[float] topt=None, Optional[float] dhd=None)
Set Jmax with temperature response (mimics C++ overloads).
Temperature response parameters for photosynthetic processes.
float dHa
Activation energy (rate of increase parameter)
float dHd
Deactivation energy (rate of decrease parameter)
__post_init__(self)
Validate parameter values after initialization.
float Topt
Optimum temperature in Kelvin (10000K means no optimum)
List[str] get_available_species()
Get list of available species in the photosynthesis library.
str validate_species_name(str species)
Validate and normalize species name for photosynthesis library.
dict get_species_aliases()
Get dictionary of species aliases.