0.1.8
Loading...
Searching...
No Matches
PhotosynthesisModel.py
Go to the documentation of this file.
1"""
2PhotosynthesisModel Plugin for PyHelios.
3
4This module provides a high-level interface to the Helios photosynthesis modeling
5plugin, enabling simulation of plant photosynthesis processes using both empirical
6and mechanistic models.
7"""
8
9from typing import List, Optional, Union
10from .Context import Context
11from .wrappers import UPhotosynthesisWrapper as photosynthesis_wrapper
12from .types.photosynthesis import (
13 PhotosyntheticTemperatureResponseParameters,
14 EmpiricalModelCoefficients,
15 FarquharModelCoefficients,
16 PHOTOSYNTHESIS_SPECIES,
17 validate_species_name,
18 get_available_species,
19 get_species_aliases
20)
21from .validation.plugin_decorators import (
22 validate_photosynthesis_species_params,
23 validate_empirical_model_params,
24 validate_farquhar_model_params,
25 validate_photosynthesis_uuid_params
26)
27
28
29class PhotosynthesisModelError(Exception):
30 """Exception raised by PhotosynthesisModel operations."""
31 pass
32
33
35 """
36 High-level interface for Helios photosynthesis modeling.
37
38 The PhotosynthesisModel provides methods for configuring and running
39 photosynthesis simulations using various models including empirical
40 and mechanistic (Farquhar-von Caemmerer-Berry) approaches.
41
42 Features:
43 - Support for empirical and FvCB photosynthesis models
44 - Built-in species library with 21+ plant species
45 - Comprehensive parameter validation
46 - Context manager support for proper cleanup
47
48 Example:
49 >>> from pyhelios import Context, PhotosynthesisModel
50 >>> from pyhelios.types import EmpiricalModelCoefficients
51 >>> context = Context()
52 >>> with PhotosynthesisModel(context) as photosynthesis:
53 ... # Configure empirical model
54 ... coeffs = EmpiricalModelCoefficients(
55 ... Tref=298.0, # Reference temperature (K)
56 ... Ci_ref=290.0, # Reference CO2 concentration (μmol/mol)
57 ... Asat=20.0, # Light-saturated photosynthesis rate (μmol/m²/s)
58 ... theta=65.0 # Light response curvature (W/m²)
59 ... )
60 ... photosynthesis.setEmpiricalModelCoefficients(coeffs)
61 ... photosynthesis.run()
62
63 Available species can be queried using:
64 >>> PhotosynthesisModel.get_available_species()
65 ['ALMOND', 'APPLE', 'AVOCADO', ...]
66 """
67
68 def __init__(self, context: Context):
69 """
70 Initialize PhotosynthesisModel.
71
72 Args:
73 context: PyHelios Context instance containing the 3D geometry
74
75 Raises:
76 PhotosynthesisModelError: If plugin is not available or initialization fails
77 """
78 if not isinstance(context, Context):
80 f"Context parameter must be a Context instance, got {type(context).__name__}"
81 )
82
83 self.context = context
84 self._native_ptr = None
85
86 try:
87 # Get the native context pointer
88 context_ptr = self.context.getNativePtr()
89 if context_ptr is None:
90 raise PhotosynthesisModelError("Context has no native pointer - context may not be properly initialized")
91
92 # Create the photosynthesis model
93 self._native_ptr = photosynthesis_wrapper.createPhotosynthesisModel(context_ptr)
94 if self._native_ptr is None:
95 raise PhotosynthesisModelError("Failed to create photosynthesis model")
96
97 except Exception as e:
98 if "photosynthesis plugin is not available" in str(e).lower():
100 "Photosynthesis plugin is not available. "
101 "Please rebuild PyHelios with photosynthesis plugin enabled:\n"
102 " build_scripts/build_helios --plugins photosynthesis"
103 ) from e
104 elif "mock mode" in str(e).lower():
106 "PhotosynthesisModel requires native Helios libraries. "
107 "Currently running in mock mode. Please build native libraries:\n"
108 " build_scripts/build_helios --plugins photosynthesis"
109 ) from e
110 else:
111 raise PhotosynthesisModelError(f"Failed to initialize PhotosynthesisModel: {e}") from e
112
113 def __enter__(self):
114 """Context manager entry."""
115 return self
116
117 def __exit__(self, exc_type, exc_value, traceback):
118 """Context manager exit with cleanup."""
119 self.cleanup()
120
121 def cleanup(self):
122 """Clean up native resources."""
123 if hasattr(self, '_native_ptr') and self._native_ptr is not None:
124 try:
125 photosynthesis_wrapper.destroyPhotosynthesisModel(self._native_ptr)
126 except Exception:
127 pass # Ignore cleanup errors
128 finally:
129 self._native_ptr = None
130
131 def get_native_ptr(self):
132 """Get the native C++ pointer for advanced operations."""
133 return self._native_ptr
134
135 def __del__(self):
136 """Destructor to ensure cleanup."""
137 self.cleanup()
138
139 # Model Configuration
141 """
142 Set the photosynthesis model type to empirical.
143
144 The empirical model uses light response curves with saturation kinetics.
145 """
146 photosynthesis_wrapper.setModelTypeEmpirical(self._native_ptr)
147
148 def setModelTypeFarquhar(self):
149 """
150 Set the photosynthesis model type to Farquhar-von Caemmerer-Berry.
151
152 The FvCB model is a mechanistic model accounting for biochemical
153 limitations of C3 photosynthesis.
154 """
155 photosynthesis_wrapper.setModelTypeFarquhar(self._native_ptr)
156
157 # Model Execution
158 def run(self):
159 """
160 Run photosynthesis calculations for all primitives in the context.
161
162 The model must be configured with appropriate coefficients before running.
163 """
164 photosynthesis_wrapper.run(self._native_ptr)
165
166 @validate_photosynthesis_uuid_params
167 def runForPrimitives(self, uuids: Union[List[int], int]):
168 """
169 Run photosynthesis calculations for specific primitives.
170
171 Args:
172 uuids: Single UUID (integer) or list of UUIDs for primitives
173 """
174 if isinstance(uuids, int):
175 uuids = [uuids]
176 photosynthesis_wrapper.runForUUIDs(self._native_ptr, uuids)
178 # Species Configuration
179 @validate_photosynthesis_species_params
180 def setSpeciesCoefficients(self, species: str, uuids: Optional[List[int]] = None):
181 """
182 Set Farquhar model coefficients from built-in species library.
183
184 Args:
185 species: Species name from the built-in library
186 uuids: Optional list of primitive UUIDs. If None, applies to all primitives.
187
188 Example:
189 >>> model.setSpeciesCoefficients("APPLE")
190 >>> model.setSpeciesCoefficients("SOYBEAN", [uuid1, uuid2])
191 """
192 if uuids is None:
193 photosynthesis_wrapper.setFarquharCoefficientsFromLibrary(self._native_ptr, species)
194 else:
195 photosynthesis_wrapper.setFarquharCoefficientsFromLibraryForUUIDs(self._native_ptr, species, uuids)
196
197 def setFarquharCoefficientsFromLibrary(self, species: str, uuids: Optional[List[int]] = None):
198 """
199 Set Farquhar model coefficients from built-in species library.
200
201 This method matches the C++ API naming: setFarquharCoefficientsFromLibrary()
202
203 Args:
204 species: Species name from the built-in library
205 uuids: Optional list of primitive UUIDs. If None, applies to all primitives.
206
207 Example:
208 >>> model.setFarquharCoefficientsFromLibrary("APPLE")
209 >>> model.setFarquharCoefficientsFromLibrary("SOYBEAN", [uuid1, uuid2])
210 """
211 if uuids is None:
212 photosynthesis_wrapper.setFarquharCoefficientsFromLibrary(self._native_ptr, species)
213 else:
214 photosynthesis_wrapper.setFarquharCoefficientsFromLibraryForUUIDs(self._native_ptr, species, uuids)
215
216 def getSpeciesCoefficients(self, species: str) -> List[float]:
217 """
218 Get Farquhar model coefficients for a species from the library.
219
220 Args:
221 species: Species name
222
223 Returns:
224 List of Farquhar model coefficients for the species
225 """
226 species = validate_species_name(species)
227 return photosynthesis_wrapper.getFarquharCoefficientsFromLibrary(self._native_ptr, species)
228
229 @staticmethod
230 def get_available_species() -> List[str]:
231 """
232 Static method to get available species without creating a model instance.
234 Returns:
235 List of species names available in the photosynthesis library
236 """
237 return get_available_species()
238
239 @staticmethod
240 def get_species_aliases() -> dict:
241 """
242 Static method to get species aliases mapping.
243
244 Returns:
245 Dictionary mapping aliases to canonical species names
246 """
247 return get_species_aliases()
248
249 # Model Coefficient Configuration
250 @validate_empirical_model_params
251 def setEmpiricalModelCoefficients(self, coefficients: EmpiricalModelCoefficients,
252 uuids: Optional[List[int]] = None):
253 """
254 Set empirical model coefficients.
255
256 Args:
257 coefficients: EmpiricalModelCoefficients instance with model parameters
258 uuids: Optional list of primitive UUIDs. If None, applies to all primitives.
259 """
260 # Convert to list format expected by C++ interface
261 coeff_list = coefficients.to_array()
262
263 if uuids is None:
264 photosynthesis_wrapper.setEmpiricalModelCoefficients(self._native_ptr, coeff_list)
265 else:
266 photosynthesis_wrapper.setEmpiricalModelCoefficientsForUUIDs(self._native_ptr, coeff_list, uuids)
267
268 @validate_farquhar_model_params
269 def setFarquharModelCoefficients(self, coefficients: FarquharModelCoefficients,
270 uuids: Optional[List[int]] = None):
271 """
272 Set Farquhar model coefficients.
273
274 Args:
275 coefficients: FarquharModelCoefficients instance with FvCB parameters
276 uuids: Optional list of primitive UUIDs. If None, applies to all primitives.
277 """
278 # Convert to list format expected by C++ interface
279 coeff_list = coefficients.to_array()
280
281 if uuids is None:
282 photosynthesis_wrapper.setFarquharModelCoefficients(self._native_ptr, coeff_list)
283 else:
284 photosynthesis_wrapper.setFarquharModelCoefficientsForUUIDs(self._native_ptr, coeff_list, uuids)
285
286 # Individual Farquhar Parameter Setting
287 def setVcmax(self, vcmax: float, uuids: List[int], dha: Optional[float] = None,
288 topt: Optional[float] = None, dhd: Optional[float] = None):
289 """
290 Set maximum carboxylation rate for Farquhar model.
291
292 This method modifies only the Vcmax parameter while preserving all
293 other existing Farquhar model parameters for each primitive.
294
295 Args:
296 vcmax: Maximum carboxylation rate at 25°C (μmol m⁻² s⁻¹)
297 uuids: List of primitive UUIDs to modify (required)
298 dha: Activation energy (optional, kJ/mol)
299 topt: Optimal temperature (optional, °C)
300 dhd: Deactivation energy (optional, kJ/mol)
301
302 Note:
303 Primitives must have existing Farquhar model coefficients set before
304 calling this method. Use setFarquharCoefficientsFromLibrary() first
305 if needed. To modify all primitives, use setFarquharModelCoefficients()
306 with complete coefficient objects.
307 """
308 from .types import FarquharModelCoefficients
309
310 # For each UUID, get existing coefficients, modify Vcmax, then set back
311 for uuid in uuids:
312 # Get existing coefficients as raw array
313 existing_array = self.getFarquharModelCoefficients(uuid)
315 # Create new coefficient object from existing values
316 existing_coeffs = FarquharModelCoefficients.from_array(existing_array)
317
318 # Modify only Vcmax parameter using the temperature response
319 if dha is None:
320 existing_coeffs.Vcmax = vcmax
321 else:
322 # Create temperature response object and set it
323 from .types import PhotosyntheticTemperatureResponseParameters
324 if dhd is None and topt is None:
325 temp_response = PhotosyntheticTemperatureResponseParameters(vcmax, dha)
326 elif dhd is None:
327 temp_response = PhotosyntheticTemperatureResponseParameters(vcmax, dha, topt)
328 else:
329 temp_response = PhotosyntheticTemperatureResponseParameters(vcmax, dha, topt, dhd)
330
331 # Set the temperature response values
332 existing_coeffs.Vcmax = temp_response.value_at_25C
333 # Note: Temperature response parameters would need to be stored separately
334 # For now, just set the basic value
335 existing_coeffs.Vcmax = vcmax
336
337 # Set the modified coefficients back for this UUID
338 self.setFarquharModelCoefficients(existing_coeffs, [uuid])
339
340 def setJmax(self, jmax: float, uuids: List[int], dha: Optional[float] = None,
341 topt: Optional[float] = None, dhd: Optional[float] = None):
342 """
343 Set maximum electron transport rate for Farquhar model.
344
345 This method modifies only the Jmax parameter while preserving all
346 other existing Farquhar model parameters for each primitive.
347
348 Args:
349 jmax: Maximum electron transport rate at 25°C (μmol m⁻² s⁻¹)
350 uuids: List of primitive UUIDs to modify (required)
351 dha: Activation energy (optional, kJ/mol)
352 topt: Optimal temperature (optional, °C)
353 dhd: Deactivation energy (optional, kJ/mol)
354
355 Note:
356 Primitives must have existing Farquhar model coefficients set before
357 calling this method. Use setFarquharCoefficientsFromLibrary() first
358 if needed. To modify all primitives, use setFarquharModelCoefficients()
359 with complete coefficient objects.
360 """
361 from .types import FarquharModelCoefficients
362
363 # For each UUID, get existing coefficients, modify Jmax, then set back
364 for uuid in uuids:
365 # Get existing coefficients as raw array
366 existing_array = self.getFarquharModelCoefficients(uuid)
368 # Create new coefficient object from existing values
369 existing_coeffs = FarquharModelCoefficients.from_array(existing_array)
370
371 # Modify only Jmax parameter
372 existing_coeffs.Jmax = jmax
373
374 # Set the modified coefficients back for this UUID
375 self.setFarquharModelCoefficients(existing_coeffs, [uuid])
376
377 def setDarkRespiration(self, respiration: float, uuids: List[int], dha: Optional[float] = None,
378 topt: Optional[float] = None, dhd: Optional[float] = None):
379 """
380 Set dark respiration rate.
381
382 This method modifies only the Rd parameter while preserving all
383 other existing Farquhar model parameters for each primitive.
384
385 Args:
386 respiration: Dark respiration rate at 25°C (μmol m⁻² s⁻¹)
387 uuids: List of primitive UUIDs to modify (required)
388 dha: Activation energy (optional, kJ/mol)
389 topt: Optimal temperature (optional, °C)
390 dhd: Deactivation energy (optional, kJ/mol)
391
392 Note:
393 Primitives must have existing Farquhar model coefficients set before
394 calling this method. Use setFarquharCoefficientsFromLibrary() first
395 if needed. To modify all primitives, use setFarquharModelCoefficients()
396 with complete coefficient objects.
397 """
398 from .types import FarquharModelCoefficients
399
400 # For each UUID, get existing coefficients, modify Rd, then set back
401 for uuid in uuids:
402 # Get existing coefficients as raw array
403 existing_array = self.getFarquharModelCoefficients(uuid)
405 # Create new coefficient object from existing values
406 existing_coeffs = FarquharModelCoefficients.from_array(existing_array)
407
408 # Modify only Rd parameter
409 existing_coeffs.Rd = respiration
410
411 # Set the modified coefficients back for this UUID
412 self.setFarquharModelCoefficients(existing_coeffs, [uuid])
413
414 def setQuantumEfficiency(self, efficiency: float, uuids: List[int], dha: Optional[float] = None,
415 topt: Optional[float] = None, dhd: Optional[float] = None):
416 """
417 Set quantum efficiency of photosystem II.
418
419 This method modifies only the alpha parameter while preserving all
420 other existing Farquhar model parameters for each primitive.
421
422 Args:
423 efficiency: Quantum efficiency at 25°C (dimensionless, 0-1)
424 uuids: List of primitive UUIDs to modify (required)
425 dha: Activation energy (optional, kJ/mol)
426 topt: Optimal temperature (optional, °C)
427 dhd: Deactivation energy (optional, kJ/mol)
428
429 Note:
430 Primitives must have existing Farquhar model coefficients set before
431 calling this method. Use setFarquharCoefficientsFromLibrary() first
432 if needed. To modify all primitives, use setFarquharModelCoefficients()
433 with complete coefficient objects.
434 """
435 from .types import FarquharModelCoefficients
436
437 # For each UUID, get existing coefficients, modify alpha, then set back
438 for uuid in uuids:
439 # Get existing coefficients as raw array
440 existing_array = self.getFarquharModelCoefficients(uuid)
442 # Create new coefficient object from existing values
443 existing_coeffs = FarquharModelCoefficients.from_array(existing_array)
444
445 # Modify only alpha parameter
446 existing_coeffs.alpha = efficiency
447
448 # Set the modified coefficients back for this UUID
449 self.setFarquharModelCoefficients(existing_coeffs, [uuid])
450
451 def setLightResponseCurvature(self, curvature: float, uuids: List[int], dha: Optional[float] = None,
452 topt: Optional[float] = None, dhd: Optional[float] = None):
453 """
454 Set light response curvature parameter.
455
456 This method modifies only the theta parameter while preserving all
457 other existing Farquhar model parameters for each primitive.
458
459 Args:
460 curvature: Light response curvature at 25°C (dimensionless)
461 uuids: List of primitive UUIDs to modify (required)
462 dha: Activation energy (optional, kJ/mol)
463 topt: Optimal temperature (optional, °C)
464 dhd: Deactivation energy (optional, kJ/mol)
465
466 Note:
467 Primitives must have existing Farquhar model coefficients set before
468 calling this method. Use setFarquharCoefficientsFromLibrary() first
469 if needed. To modify all primitives, use setFarquharModelCoefficients()
470 with complete coefficient objects.
471
472 Note:
473 The theta parameter is stored in the coefficient array but may not be
474 directly exposed in the current FarquharModelCoefficients structure.
475 This method sets the basic curvature value.
476 """
477 from .types import FarquharModelCoefficients
478
479 # For each UUID, get existing coefficients, modify theta/curvature, then set back
480 for uuid in uuids:
481 # Get existing coefficients as raw array
482 existing_array = self.getFarquharModelCoefficients(uuid)
484 # Create new coefficient object from existing values
485 existing_coeffs = FarquharModelCoefficients.from_array(existing_array)
486
487 # Note: theta/curvature parameter mapping would need to be checked
488 # For now, this is a placeholder - the actual field mapping needs verification
489 # existing_coeffs.theta = curvature # This field may not exist
490
491 # Set the modified coefficients back for this UUID
492 self.setFarquharModelCoefficients(existing_coeffs, [uuid])
493
494 # Results and Output
495 def getEmpiricalModelCoefficients(self, uuid: int) -> List[float]:
496 """
497 Get empirical model coefficients for a specific primitive.
498
499 Args:
500 uuid: Primitive UUID
501
502 Returns:
503 List of empirical model coefficients
504 """
505 return photosynthesis_wrapper.getEmpiricalModelCoefficients(self._native_ptr, uuid)
506
507 def getFarquharModelCoefficients(self, uuid: int) -> List[float]:
508 """
509 Get Farquhar model coefficients for a specific primitive.
510
511 Args:
512 uuid: Primitive UUID
513
514 Returns:
515 List of Farquhar model coefficients
516 """
517 return photosynthesis_wrapper.getFarquharModelCoefficients(self._native_ptr, uuid)
518
519 def exportResults(self, label: str):
520 """
521 Export photosynthesis results with optional label.
522
523 Args:
524 label: Data label for export
525 """
526 photosynthesis_wrapper.optionalOutputPrimitiveData(self._native_ptr, label)
527
528 # Model Information and Utilities
529 def enableMessages(self):
530 """Enable photosynthesis model status messages."""
531 photosynthesis_wrapper.enableMessages(self._native_ptr)
532
534 """Disable photosynthesis model status messages."""
535 photosynthesis_wrapper.disableMessages(self._native_ptr)
536
537 def printModelReport(self, uuids: Optional[List[int]] = None):
538 """
539 Print model configuration report.
540
541 Args:
542 uuids: Optional list of UUIDs. If None, prints report for all primitives.
543 """
544 if uuids is None:
545 photosynthesis_wrapper.printDefaultValueReport(self._native_ptr)
546 else:
547 photosynthesis_wrapper.printDefaultValueReportForUUIDs(self._native_ptr, uuids)
548
549 # Utility Methods
550 def validateConfiguration(self) -> bool:
551 """
552 Basic validation that model has been configured.
553
554 Returns:
555 True if model appears to be configured (has native pointer)
556 """
557 return self._native_ptr is not None
558
559 def resetModel(self):
560 """
561 Reset the model by recreating it.
562 Note: This will clear all configured parameters.
563 """
564 if self._native_ptr is not None:
565 old_ptr = self._native_ptr
566 try:
567 context_ptr = self.context.getNativePtr()
568 self._native_ptr = photosynthesis_wrapper.createPhotosynthesisModel(context_ptr)
569 finally:
570 # Clean up old pointer
571 try:
572 photosynthesis_wrapper.destroyPhotosynthesisModel(old_ptr)
573 except Exception:
574 pass
Exception raised by PhotosynthesisModel operations.
High-level interface for Helios photosynthesis modeling.
disableMessages(self)
Disable photosynthesis model status messages.
printModelReport(self, Optional[List[int]] uuids=None)
Print model configuration report.
enableMessages(self)
Enable photosynthesis model status messages.
bool validateConfiguration(self)
Basic validation that model has been configured.
setFarquharCoefficientsFromLibrary(self, str species, Optional[List[int]] uuids=None)
Set Farquhar model coefficients from built-in species library.
setJmax(self, float jmax, List[int] uuids, Optional[float] dha=None, Optional[float] topt=None, Optional[float] dhd=None)
Set maximum electron transport rate for Farquhar model.
List[float] getFarquharModelCoefficients(self, int uuid)
Get Farquhar model coefficients for a specific primitive.
get_native_ptr(self)
Get the native C++ pointer for advanced operations.
setDarkRespiration(self, float respiration, List[int] uuids, Optional[float] dha=None, Optional[float] topt=None, Optional[float] dhd=None)
Set dark respiration rate.
setModelTypeFarquhar(self)
Set the photosynthesis model type to Farquhar-von Caemmerer-Berry.
setVcmax(self, float vcmax, List[int] uuids, Optional[float] dha=None, Optional[float] topt=None, Optional[float] dhd=None)
Set maximum carboxylation rate for Farquhar model.
setModelTypeEmpirical(self)
Set the photosynthesis model type to empirical.
exportResults(self, str label)
Export photosynthesis results with optional label.
resetModel(self)
Reset the model by recreating it.
setQuantumEfficiency(self, float efficiency, List[int] uuids, Optional[float] dha=None, Optional[float] topt=None, Optional[float] dhd=None)
Set quantum efficiency of photosystem II.
setEmpiricalModelCoefficients(self, EmpiricalModelCoefficients coefficients, Optional[List[int]] uuids=None)
Set empirical model coefficients.
dict get_species_aliases()
Static method to get species aliases mapping.
run(self)
Run photosynthesis calculations for all primitives in the context.
runForPrimitives(self, Union[List[int], int] uuids)
Run photosynthesis calculations for specific primitives.
setFarquharModelCoefficients(self, FarquharModelCoefficients coefficients, Optional[List[int]] uuids=None)
Set Farquhar model coefficients.
List[str] get_available_species()
Static method to get available species without creating a model instance.
List[float] getEmpiricalModelCoefficients(self, int uuid)
Get empirical model coefficients for a specific primitive.
List[float] getSpeciesCoefficients(self, str species)
Get Farquhar model coefficients for a species from the library.
setSpeciesCoefficients(self, str species, Optional[List[int]] uuids=None)
Set Farquhar model coefficients from built-in species library.
__exit__(self, exc_type, exc_value, traceback)
Context manager exit with cleanup.
setLightResponseCurvature(self, float curvature, List[int] uuids, Optional[float] dha=None, Optional[float] topt=None, Optional[float] dhd=None)
Set light response curvature parameter.
Temperature response parameters for photosynthetic processes.