0.1.33
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# Topt sentinel: PhotosyntheticTemperatureResponseParameters stores Topt in Kelvin and uses
57# 10000 K to mean "no optimum". After conversion to Celsius that is ~9726.85, far above the
58# 100 C that helios-core's own validateOptimalTemperature accepts, so any value at or above
59# 200 C in the flat array must be the sentinel rather than user data. Mirrors
60# C4_NO_OPTIMUM_TOPT_C in native/src/pyhelios_wrapper_photosynthesis.cpp.
61_NO_OPTIMUM_TOPT_C = 200.0
62
63
64@dataclass
66 """
67 Temperature response parameters for photosynthetic processes.
68
69 These parameters define how photosynthetic rates vary with temperature
70 using the modified Arrhenius equation.
71
72 Attributes:
73 value_at_25C: Value of the parameter at 25°C
74 dHa: Activation energy (rate of increase parameter)
75 dHd: Deactivation energy (rate of decrease parameter)
76 Topt: Optimum temperature in Kelvin (10000K means no optimum)
77 """
78 value_at_25C: float = 100.0
79 dHa: float = 60.0
80 dHd: float = 600.0
81 Topt: float = 10000.0 # Very high = no temperature optimum
82
83 def __post_init__(self):
84 """Validate parameter values after initialization."""
85 if not math.isfinite(self.value_at_25C):
86 raise ValueError("value_at_25C must be finite")
87 if not math.isfinite(self.dHa) or self.dHa < 0:
88 raise ValueError("dHa must be finite and non-negative")
89 if not math.isfinite(self.dHd) or self.dHd < 0:
90 raise ValueError("dHd must be finite and non-negative")
91 if not math.isfinite(self.Topt) or self.Topt < 0:
92 raise ValueError("Topt must be finite and non-negative")
93 # Mirrors validateDeactivationEnergy in helios-core 1.3.80: the peaked Arrhenius form
94 # evaluates ln(dHd/dHa - 1), which is undefined unless dHd > dHa. dHa <= 0 means no
95 # Arrhenius term is applied, so dHd is unused.
96 if self.dHa > 0 and self.dHd <= self.dHa:
97 raise ValueError(
98 f"Deactivation energy dHd must be strictly greater than activation energy dHa "
99 f"for a peaked temperature response. Received dHa = {self.dHa} kJ/mol and "
100 f"dHd = {self.dHd} kJ/mol. The peaked Arrhenius form evaluates "
101 f"ln(dHd/dHa - 1), which is undefined when dHd <= dHa. Increase dHd "
102 f"(a typical value is 10*dHa, or 200-600 kJ/mol) or omit dHd to use the default."
103 )
104
105
106@dataclass
108 """
109 Empirical photosynthesis model coefficients.
110
111 This model uses empirical relationships to estimate photosynthetic
112 rates based on environmental conditions.
113
114 Attributes:
115 Tref: Reference temperature (K)
116 Ci_ref: Reference CO2 concentration (μmol CO2/mol air)
117 Asat: Light-saturated photosynthetic rate (μmol/m²/s)
118 theta: Half-saturation light level (W/m²)
119 Tmin: Minimum temperature for photosynthesis (K)
120 Topt: Optimum temperature for photosynthesis (K)
121 q: Temperature response parameter (unitless)
122 R: Respiration temperature coefficient (μmol·K^0.5/m²/s)
123 ER: Respiration activation energy (1/K)
124 kC: CO2 response coefficient (unitless)
125 """
126 Tref: float = 298.0 # K
127 Ci_ref: float = 290.0 # μmol CO2/mol air
128 Asat: float = 18.18 # μmol/m²/s
129 theta: float = 62.03 # W/m²
130 Tmin: float = 290.0 # K
131 Topt: float = 303.0 # K
132 q: float = 0.344 # unitless
133 R: float = 1.663e5 # μmol·K^0.5/m²/s
134 ER: float = 3740.0 # 1/K
135 kC: float = 0.791 # unitless
136
137 def __post_init__(self):
138 """Validate parameter values after initialization."""
139 if self.Tref <= 0:
140 raise ValueError("Reference temperature must be positive")
141 if self.Ci_ref <= 0:
142 raise ValueError("Reference CO2 concentration must be positive")
143 if self.Asat < 0:
144 raise ValueError("Light-saturated photosynthetic rate cannot be negative")
145 if self.theta <= 0:
146 raise ValueError("Half-saturation light level must be positive")
147 if self.Tmin <= 0:
148 raise ValueError("Minimum temperature must be positive")
149 if self.Topt <= 0:
150 raise ValueError("Optimum temperature must be positive")
151 if self.Tmin >= self.Topt:
152 raise ValueError("Minimum temperature must be less than optimum temperature")
153 if self.q <= 0:
154 raise ValueError("Temperature response parameter must be positive")
155 if self.R < 0:
156 raise ValueError("Respiration coefficient cannot be negative")
157 if self.ER < 0:
158 raise ValueError("Respiration activation energy cannot be negative")
159 if self.kC < 0:
160 raise ValueError("CO2 response coefficient cannot be negative")
161 # Mirrors the coefficient checks helios-core 1.3.80 added to the empirical
162 # temperature response f_T. Before 1.3.80 the Tmin/Topt/Tref/q coefficients were
163 # completely inert; now they drive f_T, and these two combinations make its
164 # reference denominator zero, giving an infinite or NaN assimilation rate.
165 if self.Tref <= self.Tmin:
166 raise ValueError(
167 f"Reference temperature Tref ({self.Tref} K) must be greater than the minimum "
168 f"temperature Tmin ({self.Tmin} K); otherwise the empirical temperature "
169 f"response f_T has a zero or negative reference denominator."
170 )
171 denom_ref = (1.0 + self.q) * self.Topt - self.Tmin - self.q * self.Tref
172 if abs(denom_ref) < 1e-6:
173 raise ValueError(
174 f"Empirical temperature response coefficients are degenerate: "
175 f"(1+q)*Topt - Tmin - q*Tref = {denom_ref}, which makes the reference "
176 f"denominator of f_T zero. Adjust Topt, Tmin, Tref or q."
177 )
178
179 def to_array(self) -> List[float]:
180 """Convert to float array for C++ interface."""
181 return [
182 self.Tref, self.Ci_ref, self.Asat, self.theta, self.Tmin,
183 self.Topt, self.q, self.R, self.ER, self.kC
184 ]
185
186 @classmethod
187 def from_array(cls, coefficients: List[float]) -> 'EmpiricalModelCoefficients':
188 """Create from float array (from C++ interface)."""
189 if len(coefficients) < 10:
190 raise ValueError("Need at least 10 coefficients for empirical model")
191 return cls(
192 Tref=coefficients[0], Ci_ref=coefficients[1], Asat=coefficients[2],
193 theta=coefficients[3], Tmin=coefficients[4], Topt=coefficients[5],
194 q=coefficients[6], R=coefficients[7], ER=coefficients[8], kC=coefficients[9]
195 )
196
197
198@dataclass
200 """
201 Farquhar-von Caemmerer-Berry photosynthesis model coefficients.
202
203 This model provides a mechanistic description of leaf photosynthesis
204 based on biochemical limitations and temperature responses.
205
206 Core Parameters (at 25°C):
207 Vcmax: Maximum carboxylation rate (μmol/m²/s, -1 = uninitialized)
208 Jmax: Maximum electron transport rate (μmol/m²/s, -1 = uninitialized)
209 alpha: Quantum efficiency of photosystem II (μmol electrons/μmol photons)
210 Rd: Dark respiration rate (μmol/m²/s, -1 = uninitialized)
211 O: Ambient oxygen concentration (mmol/mol)
212 TPU_flag: Enable triose phosphate utilization limitation (0/1)
213
214 Temperature Response Parameters:
215 c_*: Scaling factor for Arrhenius equation
216 dH_*: Activation energy for temperature response
217 """
218 # Core parameters at 25°C
219 Vcmax: float = -1.0 # μmol/m²/s (uninitialized)
220 Jmax: float = -1.0 # μmol/m²/s (uninitialized)
221 alpha: float = -1.0 # unitless (uninitialized)
222 Rd: float = -1.0 # μmol/m²/s (uninitialized)
223 O: float = 213.5 # ambient oxygen concentration (mmol/mol)
224 TPU_flag: int = 0 # enable TPU limitation
225
226 # Temperature scaling factors (c_*)
227 c_Rd: float = 18.72
228 c_Vcmax: float = 26.35
229 c_Jmax: float = 18.86
230 c_Gamma: float = 19.02
231 c_Kc: float = 38.05
232 c_Ko: float = 20.30
233
234 # Activation energies (dH_*)
235 dH_Rd: float = 46.39
236 dH_Vcmax: float = 65.33
237 dH_Jmax: float = 46.36
238 dH_Gamma: float = 37.83
239 dH_Kc: float = 79.43
240 dH_Ko: float = 36.38
241
242 # Mesophyll conductance gm (helios-core 1.3.72+). The default math.inf reproduces
243 # the legacy Cc = Ci behaviour (no mesophyll diffusion limitation). When packed into
244 # the flat coefficient array, slots [18..21] are (gm_at_25C, dHa, Topt_C, dHd) using
245 # the same -1 sentinel convention as the C4 model: dHa < 0 → constant gm with no
246 # temperature response.
247 gm_at_25C: float = float('inf') # mol CO2 / m^2 / s / bar
248 dHa_gm: float = -1.0 # kJ/mol; -1 disables temperature response
249 Topt_gm_C: float = -1.0 # Celsius; -1 → monotonic Arrhenius
250 dHd_gm: float = -1.0 # kJ/mol; -1 → default
252 # Temperature response parameter containers
253 _vcmax_temp_response: Optional[PhotosyntheticTemperatureResponseParameters] = field(default=None, init=False)
254 _jmax_temp_response: Optional[PhotosyntheticTemperatureResponseParameters] = field(default=None, init=False)
255 _rd_temp_response: Optional[PhotosyntheticTemperatureResponseParameters] = field(default=None, init=False)
256 _alpha_temp_response: Optional[PhotosyntheticTemperatureResponseParameters] = field(default=None, init=False)
257 _theta_temp_response: Optional[PhotosyntheticTemperatureResponseParameters] = field(default=None, init=False)
259 def __post_init__(self):
260 """Validate parameter values after initialization."""
261 if self.O <= 0:
262 raise ValueError("Oxygen concentration must be positive")
263 if self.TPU_flag not in (0, 1):
264 raise ValueError("TPU_flag must be 0 or 1")
265
266 # Validate temperature parameters
267 for param_name, value in [
268 ('c_Rd', self.c_Rd), ('c_Vcmax', self.c_Vcmax), ('c_Jmax', self.c_Jmax),
269 ('c_Gamma', self.c_Gamma), ('c_Kc', self.c_Kc), ('c_Ko', self.c_Ko),
270 ('dH_Rd', self.dH_Rd), ('dH_Vcmax', self.dH_Vcmax), ('dH_Jmax', self.dH_Jmax),
271 ('dH_Gamma', self.dH_Gamma), ('dH_Kc', self.dH_Kc), ('dH_Ko', self.dH_Ko)
272 ]:
273 if not math.isfinite(value):
274 raise ValueError(f"Temperature parameter {param_name} must be finite")
275
276 def setVcmax(self, vcmax_at_25c: float, dha: Optional[float] = None,
277 topt: Optional[float] = None, dhd: Optional[float] = None) -> None:
278 """Set Vcmax with temperature response (mimics C++ overloads)."""
279 if dha is None:
280 # 1-parameter version
282 elif topt is None:
283 # 2-parameter version
285 elif dhd is None:
286 # 3-parameter version
287 # C++ defaults dHd to 10*dHa for the 3-argument peaked form.
289 vcmax_at_25c, dHa=dha, dHd=10.0 * dha, Topt=273.15 + topt)
290 else:
291 # 4-parameter version
293 vcmax_at_25c, dHa=dha, dHd=dhd, Topt=273.15 + topt)
294
295 self.Vcmax = vcmax_at_25c
296
297 def setJmax(self, jmax_at_25c: float, dha: Optional[float] = None,
298 topt: Optional[float] = None, dhd: Optional[float] = None) -> None:
299 """Set Jmax with temperature response (mimics C++ overloads)."""
300 if dha is None:
302 elif topt is None:
304 elif dhd is None:
305 # C++ defaults dHd to 10*dHa for the 3-argument peaked form.
307 jmax_at_25c, dHa=dha, dHd=10.0 * dha, Topt=273.15 + topt)
308 else:
310 jmax_at_25c, dHa=dha, dHd=dhd, Topt=273.15 + topt)
311
312 self.Jmax = jmax_at_25c
313
314 def setRd(self, rd_at_25c: float, dha: Optional[float] = None,
315 topt: Optional[float] = None, dhd: Optional[float] = None) -> None:
316 """Set dark respiration with temperature response (mimics C++ overloads)."""
317 if dha is None:
319 elif topt is None:
321 elif dhd is None:
322 # C++ defaults dHd to 10*dHa for the 3-argument peaked form.
324 rd_at_25c, dHa=dha, dHd=10.0 * dha, Topt=273.15 + topt)
325 else:
327 rd_at_25c, dHa=dha, dHd=dhd, Topt=273.15 + topt)
328
329 self.Rd = rd_at_25c
330
331 def setQuantumEfficiency_alpha(self, alpha_at_25c: float, dha: Optional[float] = None,
332 topt: Optional[float] = None, dhd: Optional[float] = None) -> None:
333 """Set quantum efficiency with temperature response (mimics C++ overloads)."""
334 if dha is None:
336 elif topt is None:
338 elif dhd is None:
339 # C++ defaults dHd to 10*dHa for the 3-argument peaked form.
341 alpha_at_25c, dHa=dha, dHd=10.0 * dha, Topt=273.15 + topt)
342 else:
344 alpha_at_25c, dHa=dha, dHd=dhd, Topt=273.15 + topt)
345
346 self.alpha = alpha_at_25c
347
348 def setLightResponseCurvature_theta(self, theta_at_25c: float, dha: Optional[float] = None,
349 topt: Optional[float] = None, dhd: Optional[float] = None) -> None:
350 """Set light response curvature with temperature response (mimics C++ overloads)."""
351 if dha is None:
353 elif topt is None:
355 elif dhd is None:
356 # C++ defaults dHd to 10*dHa for the 3-argument peaked form.
358 theta_at_25c, dHa=dha, dHd=10.0 * dha, Topt=273.15 + topt)
359 else:
361 theta_at_25c, dHa=dha, dHd=dhd, Topt=273.15 + topt)
362
363 def getVcmaxTempResponse(self) -> PhotosyntheticTemperatureResponseParameters:
364 """Get Vcmax temperature response parameters."""
365 if self._vcmax_temp_response is None:
366 return PhotosyntheticTemperatureResponseParameters(self.Vcmax if self.Vcmax > 0 else 100.0)
367 return self._vcmax_temp_response
368
369 def getJmaxTempResponse(self) -> PhotosyntheticTemperatureResponseParameters:
370 """Get Jmax temperature response parameters."""
371 if self._jmax_temp_response is None:
372 return PhotosyntheticTemperatureResponseParameters(self.Jmax if self.Jmax > 0 else 100.0)
373 return self._jmax_temp_response
374
375 def getRdTempResponse(self) -> PhotosyntheticTemperatureResponseParameters:
376 """Get dark respiration temperature response parameters."""
377 if self._rd_temp_response is None:
378 return PhotosyntheticTemperatureResponseParameters(self.Rd if self.Rd > 0 else 1.0)
379 return self._rd_temp_response
381 def getQuantumEfficiencyTempResponse(self) -> PhotosyntheticTemperatureResponseParameters:
382 """Get quantum efficiency temperature response parameters."""
383 if self._alpha_temp_response is None:
384 return PhotosyntheticTemperatureResponseParameters(self.alpha if self.alpha > 0 else 0.3)
385 return self._alpha_temp_response
386
387 def getLightResponseCurvatureTempResponse(self) -> PhotosyntheticTemperatureResponseParameters:
388 """Get light response curvature temperature response parameters."""
389 if self._theta_temp_response is None:
391 return self._theta_temp_response
392
393 def _temp_response_block(self, response: PhotosyntheticTemperatureResponseParameters,
394 fallback_value: float) -> List[float]:
395 """Pack a temperature response into its 4-float (value, dHa, Topt_C, dHd) block.
396
397 ``Topt`` is stored in Kelvin and defaults to 10000 K to mean "no optimum". The flat
398 array carries Topt in Celsius with -1 as the no-optimum sentinel, matching
399 ``packTempResponse`` in native/src/pyhelios_wrapper_photosynthesis.cpp.
400 """
401 if response is None:
402 return [fallback_value, -1.0, -1.0, -1.0]
403 topt_c = response.Topt - 273.15
404 if topt_c >= _NO_OPTIMUM_TOPT_C:
405 topt_c = -1.0
406 return [response.value_at_25C, response.dHa, topt_c, response.dHd]
408 def to_array(self) -> List[float]:
409 """Convert to float array for C++ interface (38 floats; helios-core 1.3.80+).
410
411 Slots 0..17 are the legacy Farquhar fields (Vcmax/Jmax/alpha/Rd/O/TPU_flag plus
412 the 12 c_*/dH_* temperature constants). Slots 18..21 carry the mesophyll
413 conductance gm temperature response: (gm_at_25C, dHa, Topt_C, dHd) using the
414 -1 sentinel convention (dHa < 0 → constant, Topt_C < 0 → monotonic Arrhenius,
415 dHd < 0 → default deactivation energy). Slots 22..37 carry the same 4-float block
416 for Vcmax, Jmax, Rd and alpha in that order, and slots 38..41 the same block for the
417 light response curvature theta.
418
419 The rate blocks in slots 22..37 exist because slots 0..3 can only express a rate at
420 25 C. As of helios-core 1.3.80 the C++ setters stamp the deprecated scalar fields to
421 -1 so the temperature-response object is authoritative, and every species in the
422 library is populated through those setters — so slots 0..3 alone cannot round-trip a
423 peaked response.
424 """
425 return [
426 self.Vcmax, self.Jmax, self.alpha, self.Rd, self.O, float(self.TPU_flag),
427 # Temperature scaling factors
428 self.c_Vcmax, self.dH_Vcmax, self.c_Jmax, self.dH_Jmax,
429 self.c_Rd, self.dH_Rd, self.c_Kc, self.dH_Kc,
430 self.c_Ko, self.dH_Ko, self.c_Gamma, self.dH_Gamma,
431 # Mesophyll conductance gm temperature response (1.3.72+)
432 self.gm_at_25C, self.dHa_gm, self.Topt_gm_C, self.dHd_gm,
433 # Full rate temperature responses (1.3.80+)
436 *self._temp_response_block(self._rd_temp_response, self.Rd),
438 # Light response curvature theta temperature response (slots 38..41). Theta has no
439 # legacy scalar slot, so this block is the only representation that crosses.
441 ]
442
443 @classmethod
444 def from_array(cls, coefficients: List[float]) -> 'FarquharModelCoefficients':
445 """Create from float array (from C++ interface).
446
447 Accepts the legacy 18-float layout (pre-1.3.72), the 22-float layout with mesophyll
448 conductance gm in slots 18..21, and the 38-float layout (1.3.80+) that additionally
449 carries the Vcmax/Jmax/Rd/alpha temperature responses in slots 22..37, and the
450 42-float layout that adds the light response curvature theta in slots 38..41. Shorter
451 arrays leave the corresponding responses unset, reproducing the earlier behaviour.
452 """
453 if len(coefficients) < 18:
454 raise ValueError("Need at least 18 coefficients for Farquhar model")
455
456 gm_at_25C = coefficients[18] if len(coefficients) > 18 else float('inf')
457 dHa_gm = coefficients[19] if len(coefficients) > 19 else -1.0
458 Topt_gm_C = coefficients[20] if len(coefficients) > 20 else -1.0
459 dHd_gm = coefficients[21] if len(coefficients) > 21 else -1.0
460
461 instance = cls(
462 Vcmax=coefficients[0], Jmax=coefficients[1], alpha=coefficients[2],
463 Rd=coefficients[3], O=coefficients[4], TPU_flag=int(coefficients[5]),
464 c_Vcmax=coefficients[6], dH_Vcmax=coefficients[7],
465 c_Jmax=coefficients[8], dH_Jmax=coefficients[9],
466 c_Rd=coefficients[10], dH_Rd=coefficients[11],
467 c_Kc=coefficients[12], dH_Kc=coefficients[13],
468 c_Ko=coefficients[14], dH_Ko=coefficients[15],
469 c_Gamma=coefficients[16], dH_Gamma=coefficients[17],
470 gm_at_25C=gm_at_25C, dHa_gm=dHa_gm, Topt_gm_C=Topt_gm_C, dHd_gm=dHd_gm,
471 )
472
473 if len(coefficients) >= 38:
474 for offset, setter in ((22, instance.setVcmax), (26, instance.setJmax),
475 (30, instance.setRd), (34, instance.setQuantumEfficiency_alpha)):
476 value, dha, topt, dhd = coefficients[offset:offset + 4]
477 if dha < 0:
478 setter(value)
479 elif topt < 0:
480 setter(value, dha)
481 elif dhd < 0:
482 setter(value, dha, topt)
483 else:
484 setter(value, dha, topt, dhd)
486 if len(coefficients) >= 42:
487 value, dha, topt, dhd = coefficients[38:42]
488 if dha < 0:
489 instance.setLightResponseCurvature_theta(value)
490 elif topt < 0:
491 instance.setLightResponseCurvature_theta(value, dha)
492 elif dhd < 0:
493 instance.setLightResponseCurvature_theta(value, dha, topt)
494 else:
495 instance.setLightResponseCurvature_theta(value, dha, topt, dhd)
496
497 return instance
498
499
500def validate_species_name(species: str) -> str:
501 """
502 Validate and normalize species name for photosynthesis library.
503
504 Args:
505 species: Species name (case insensitive, supports aliases)
506
507 Returns:
508 Normalized species name
509
510 Raises:
511 ValueError: If species is not recognized
512 """
513 if not species:
514 raise ValueError("Species name cannot be empty")
515
516 # Try exact match first (case sensitive)
517 if species in PHOTOSYNTHESIS_SPECIES:
518 return species
519
520 # Try case-insensitive match
521 species_lower = species.lower()
522 if species_lower in SPECIES_ALIASES:
523 return SPECIES_ALIASES[species_lower]
524
525 # Try case-insensitive match against known species
526 for known_species in PHOTOSYNTHESIS_SPECIES:
527 if known_species.lower() == species_lower:
528 return known_species
529
530 # Species not found - provide helpful error message
531 available_species = sorted(set(list(PHOTOSYNTHESIS_SPECIES) + list(SPECIES_ALIASES.keys())))
532 raise ValueError(
533 f"Unknown species '{species}'. Available species and aliases:\n"
534 f" {', '.join(available_species[:8])}\n"
535 f" {', '.join(available_species[8:16])}\n"
536 f" {', '.join(available_species[16:])}"
537 )
538
539
540def get_available_species() -> List[str]:
541 """Get list of available species in the photosynthesis library."""
542 return sorted(PHOTOSYNTHESIS_SPECIES.copy())
543
544
546 """Get dictionary of species aliases."""
547 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 (38 floats; helios-core 1.3.80+).
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.
List[float] _temp_response_block(self, PhotosyntheticTemperatureResponseParameters response, float fallback_value)
Pack a temperature response into its 4-float (value, dHa, Topt_C, dHd) block.
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.