2High-level StomatalConductance interface for PyHelios.
4This module provides a user-friendly interface to the stomatal conductance modeling
5capabilities with graceful plugin handling and informative error messages.
9from typing
import List, Optional, Union, NamedTuple
10from contextlib
import contextmanager
12from .plugins.registry
import get_plugin_registry
13from .wrappers
import UStomatalConductanceWrapper
as stomatal_wrapper
14from .Context
import Context, check_context_alive
15from .exceptions
import HeliosError
17logger = logging.getLogger(__name__)
21 """Exception raised for StomatalConductance-specific errors."""
27 """Ball-Woodrow-Berry model coefficients."""
33 """Ball-Berry-Leuning model coefficients."""
40 """Medlyn et al. optimality model coefficients."""
46 """Buckley-Mott-Farquhar model coefficients."""
54 """Bailey model coefficients."""
64 High-level interface for stomatal conductance modeling and gas exchange calculations.
66 This class provides a user-friendly wrapper around the native Helios
67 stomatal conductance plugin with automatic plugin availability checking and
68 graceful error handling.
70 The stomatal conductance model implements five different stomatal response models:
71 - BWB: Ball, Woodrow, Berry (1987) - original model
72 - BBL: Ball, Berry, Leuning (1990, 1995) - includes VPD response
73 - MOPT: Medlyn et al. (2011) - optimality-based model
74 - BMF: Buckley, Mott, Farquhar - simplified mechanistic model
75 - BB: Bailey - hydraulic-based model
77 The plugin includes a species library with pre-calibrated coefficients for
78 common plant species (Almond, Apple, Avocado, Grape, Lemon, Olive, Walnut, etc.).
80 Both steady-state and dynamic (time-stepping) calculations are supported,
81 with configurable time constants for stomatal opening and closing dynamics.
84 - Cross-platform support (Windows, Linux, macOS)
86 - No special dependencies
87 - Stomatal conductance plugin compiled into PyHelios
90 >>> with Context() as context:
91 ... # Add leaf geometry
92 ... leaf_uuid = context.addPatch(center=[0, 0, 1], size=[0.1, 0.1])
94 ... with StomatalConductanceModel(context) as stomatal:
95 ... # Set model coefficients using species library
96 ... stomatal.setBMFCoefficientsFromLibrary("Almond")
98 ... # Run steady-state calculation
101 ... # Or run dynamic simulation with timestep
102 ... stomatal.run(dt=60.0) # 60 second timestep
104 ... # Set custom BMF coefficients for specific leaves
105 ... bmf_coeffs = BMFCoefficients(Em=258.25, i0=38.65, k=232916.82, b=609.67)
106 ... stomatal.setBMFCoefficients(bmf_coeffs, uuids=[leaf_uuid])
109 def __init__(self, context: Context):
111 Initialize StomatalConductanceModel with graceful plugin handling.
114 context: Helios Context instance
117 TypeError: If context is not a Context instance
118 StomatalConductanceModelError: If stomatal conductance plugin is not available
121 if not (hasattr(context,
'__class__')
and
122 (isinstance(context, Context)
or
123 context.__class__.__name__ ==
'Context')):
124 raise TypeError(f
"StomatalConductanceModel requires a Context instance, got {type(context).__name__}")
130 registry = get_plugin_registry()
132 if not registry.is_plugin_available(
'stomatalconductance'):
134 plugin_info = registry.get_plugin_capabilities()
135 available_plugins = registry.get_available_plugins()
138 "StomatalConductanceModel requires the 'stomatalconductance' plugin which is not available.\n\n"
139 "The stomatal conductance plugin provides gas exchange calculations using five validated models:\n"
140 "- Ball-Woodrow-Berry (BWB) - classic stomatal response\n"
141 "- Ball-Berry-Leuning (BBL) - includes vapor pressure deficit\n"
142 "- Medlyn et al. optimality (MOPT) - optimal stomatal behavior\n"
143 "- Buckley-Mott-Farquhar (BMF) - mechanistic approach\n"
144 "- Bailey (BB) - hydraulic-based model\n\n"
146 "- Species library with pre-calibrated coefficients\n"
147 "- Dynamic time-stepping with configurable time constants\n"
148 "- No GPU or special dependencies required\n\n"
149 "To enable stomatal conductance modeling:\n"
150 "1. Build PyHelios with stomatal conductance plugin:\n"
151 " build_scripts/build_helios --plugins stomatalconductance\n"
152 "2. Or build with multiple plugins:\n"
153 " build_scripts/build_helios --plugins stomatalconductance,energybalance,photosynthesis\n"
154 f
"\nCurrently available plugins: {available_plugins}"
158 alternatives = registry.suggest_alternatives(
'stomatalconductance')
160 error_msg += f
"\n\nAlternative plugins available: {alternatives}"
161 error_msg +=
"\nConsider using photosynthesis or energybalance for related plant physiology modeling."
167 self.
stomatal_model = stomatal_wrapper.createStomatalConductanceModel(context.getNativePtr())
170 "Failed to create StomatalConductanceModel instance. "
171 "This may indicate a problem with the native library."
173 logger.info(
"StomatalConductanceModel created successfully")
175 except Exception
as e:
179 """Raise if the owning Context has been destroyed (see Context.check_context_alive)."""
180 check_context_alive(getattr(self,
"context",
None),
"StomatalConductanceModel")
183 """Context manager entry."""
186 def __exit__(self, exc_type, exc_value, traceback):
187 """Context manager exit with proper cleanup."""
190 stomatal_wrapper.destroyStomatalConductanceModel(self.
stomatal_model)
191 logger.debug(
"StomatalConductanceModel destroyed successfully")
192 except Exception
as e:
193 logger.warning(f
"Error destroying StomatalConductanceModel: {e}")
198 """Destructor to ensure C++ resources freed even without 'with' statement."""
199 if hasattr(self,
'stomatal_model')
and self.
stomatal_model is not None:
201 stomatal_wrapper.destroyStomatalConductanceModel(self.
stomatal_model)
203 except Exception
as e:
205 warnings.warn(f
"Error in StomatalConductanceModel.__del__: {e}")
208 """Get the native pointer for advanced operations."""
213 Enable console output messages from the stomatal conductance model.
216 StomatalConductanceModelError: If operation fails
221 except Exception
as e:
226 Disable console output messages from the stomatal conductance model.
229 StomatalConductanceModelError: If operation fails
234 except Exception
as e:
237 def run(self, uuids: Optional[List[int]] =
None, dt: Optional[float] =
None) ->
None:
239 Run the stomatal conductance model.
241 This method supports multiple execution modes:
242 - Steady state for all primitives: run()
243 - Dynamic with timestep for all primitives: run(dt=60.0)
244 - Steady state for specific primitives: run(uuids=[1, 2, 3])
245 - Dynamic with timestep for specific primitives: run(uuids=[1, 2, 3], dt=60.0)
248 uuids: Optional list of primitive UUIDs to process. If None, processes all primitives.
249 dt: Optional timestep in seconds for dynamic simulation. If None, runs steady-state.
252 ValueError: If parameters are invalid
253 StomatalConductanceModelError: If calculation fails
256 >>> # Steady state for all primitives
259 >>> # Dynamic simulation with 60-second timestep
260 >>> stomatal.run(dt=60.0)
262 >>> # Steady state for specific leaves
263 >>> stomatal.run(uuids=[leaf1_uuid, leaf2_uuid])
265 >>> # Dynamic simulation for specific leaves
266 >>> stomatal.run(uuids=[leaf1_uuid, leaf2_uuid], dt=30.0)
270 if dt
is not None and uuids
is not None:
272 stomatal_wrapper.runForUUIDsDynamic(self.
stomatal_model, uuids, dt)
276 elif uuids
is not None:
283 except Exception
as e:
287 def setBWBCoefficients(self, coeffs: BWBCoefficients, uuids: Optional[List[int]] =
None) ->
None:
289 Set Ball-Woodrow-Berry model coefficients.
292 coeffs: BWB model coefficients (gs0, a1)
293 uuids: Optional list of primitive UUIDs. If None, applies to all primitives.
296 ValueError: If coefficients are invalid
297 StomatalConductanceModelError: If operation fails
300 >>> bwb_coeffs = BWBCoefficients(gs0=0.0733, a1=9.422)
301 >>> stomatal.setBWBCoefficients(bwb_coeffs)
303 if not isinstance(coeffs, BWBCoefficients):
304 raise ValueError(
"coeffs must be a BWBCoefficients instance")
306 raise ValueError(
"gs0 must be non-negative")
308 raise ValueError(
"a1 must be non-negative")
312 if uuids
is not None:
313 stomatal_wrapper.setBWBCoefficientsForUUIDs(self.
stomatal_model, coeffs.gs0, coeffs.a1, uuids)
315 stomatal_wrapper.setBWBCoefficients(self.
stomatal_model, coeffs.gs0, coeffs.a1)
316 except Exception
as e:
320 def setBBLCoefficients(self, coeffs: BBLCoefficients, uuids: Optional[List[int]] =
None) ->
None:
322 Set Ball-Berry-Leuning model coefficients.
325 coeffs: BBL model coefficients (gs0, a1, D0)
326 uuids: Optional list of primitive UUIDs. If None, applies to all primitives.
329 ValueError: If coefficients are invalid
330 StomatalConductanceModelError: If operation fails
333 >>> bbl_coeffs = BBLCoefficients(gs0=0.0743, a1=4.265, D0=14570.0)
334 >>> stomatal.setBBLCoefficients(bbl_coeffs)
336 if not isinstance(coeffs, BBLCoefficients):
337 raise ValueError(
"coeffs must be a BBLCoefficients instance")
339 raise ValueError(
"gs0 must be non-negative")
341 raise ValueError(
"a1 must be non-negative")
343 raise ValueError(
"D0 must be positive")
347 if uuids
is not None:
348 stomatal_wrapper.setBBLCoefficientsForUUIDs(self.
stomatal_model, coeffs.gs0, coeffs.a1, coeffs.D0, uuids)
350 stomatal_wrapper.setBBLCoefficients(self.
stomatal_model, coeffs.gs0, coeffs.a1, coeffs.D0)
351 except Exception
as e:
355 def setMOPTCoefficients(self, coeffs: MOPTCoefficients, uuids: Optional[List[int]] =
None) ->
None:
357 Set Medlyn et al. optimality model coefficients.
360 coeffs: MOPT model coefficients (gs0, g1)
361 uuids: Optional list of primitive UUIDs. If None, applies to all primitives.
364 ValueError: If coefficients are invalid
365 StomatalConductanceModelError: If operation fails
368 >>> mopt_coeffs = MOPTCoefficients(gs0=0.0825, g1=2.637)
369 >>> stomatal.setMOPTCoefficients(mopt_coeffs)
371 if not isinstance(coeffs, MOPTCoefficients):
372 raise ValueError(
"coeffs must be a MOPTCoefficients instance")
374 raise ValueError(
"gs0 must be non-negative")
376 raise ValueError(
"g1 must be positive")
380 if uuids
is not None:
381 stomatal_wrapper.setMOPTCoefficientsForUUIDs(self.
stomatal_model, coeffs.gs0, coeffs.g1, uuids)
383 stomatal_wrapper.setMOPTCoefficients(self.
stomatal_model, coeffs.gs0, coeffs.g1)
384 except Exception
as e:
388 def setBMFCoefficients(self, coeffs: BMFCoefficients, uuids: Optional[List[int]] =
None) ->
None:
390 Set Buckley-Mott-Farquhar model coefficients.
393 coeffs: BMF model coefficients (Em, i0, k, b)
394 uuids: Optional list of primitive UUIDs. If None, applies to all primitives.
397 ValueError: If coefficients are invalid
398 StomatalConductanceModelError: If operation fails
401 >>> bmf_coeffs = BMFCoefficients(Em=258.25, i0=38.65, k=232916.82, b=609.67)
402 >>> stomatal.setBMFCoefficients(bmf_coeffs)
404 if not isinstance(coeffs, BMFCoefficients):
405 raise ValueError(
"coeffs must be a BMFCoefficients instance")
407 raise ValueError(
"Em must be positive")
409 raise ValueError(
"i0 must be non-negative")
411 raise ValueError(
"k must be positive")
413 raise ValueError(
"b must be positive")
417 if uuids
is not None:
418 stomatal_wrapper.setBMFCoefficientsForUUIDs(self.
stomatal_model, coeffs.Em, coeffs.i0, coeffs.k, coeffs.b, uuids)
420 stomatal_wrapper.setBMFCoefficients(self.
stomatal_model, coeffs.Em, coeffs.i0, coeffs.k, coeffs.b)
421 except Exception
as e:
425 def setBBCoefficients(self, coeffs: BBCoefficients, uuids: Optional[List[int]] =
None) ->
None:
427 Set Bailey model coefficients.
430 coeffs: BB model coefficients (pi_0, pi_m, theta, sigma, chi)
431 uuids: Optional list of primitive UUIDs. If None, applies to all primitives.
434 ValueError: If coefficients are invalid
435 StomatalConductanceModelError: If operation fails
438 >>> bb_coeffs = BBCoefficients(pi_0=1.0, pi_m=1.67, theta=211.22, sigma=0.4408, chi=2.076)
439 >>> stomatal.setBBCoefficients(bb_coeffs)
441 if not isinstance(coeffs, BBCoefficients):
442 raise ValueError(
"coeffs must be a BBCoefficients instance")
443 if coeffs.pi_0 <= 0.0:
444 raise ValueError(
"pi_0 must be positive")
445 if coeffs.pi_m <= 0.0:
446 raise ValueError(
"pi_m must be positive")
447 if coeffs.theta <= 0.0:
448 raise ValueError(
"theta must be positive")
449 if coeffs.sigma <= 0.0:
450 raise ValueError(
"sigma must be positive")
451 if coeffs.chi <= 0.0:
452 raise ValueError(
"chi must be positive")
456 if uuids
is not None:
457 stomatal_wrapper.setBBCoefficientsForUUIDs(self.
stomatal_model, coeffs.pi_0, coeffs.pi_m, coeffs.theta, coeffs.sigma, coeffs.chi, uuids)
459 stomatal_wrapper.setBBCoefficients(self.
stomatal_model, coeffs.pi_0, coeffs.pi_m, coeffs.theta, coeffs.sigma, coeffs.chi)
460 except Exception
as e:
466 Set BMF model coefficients using the built-in species library.
469 species: Species name from the library (e.g., "Almond", "Apple", "Grape", "Walnut")
470 uuids: Optional list of primitive UUIDs. If None, applies to all primitives.
473 ValueError: If species name is invalid
474 StomatalConductanceModelError: If operation fails
477 >>> # Set coefficients for almond tree
478 >>> stomatal.setBMFCoefficientsFromLibrary("Almond")
480 >>> # Set coefficients for specific leaves only
481 >>> stomatal.setBMFCoefficientsFromLibrary("Grape", uuids=[leaf1_uuid, leaf2_uuid])
484 raise ValueError(
"Species name cannot be empty")
487 available_species = [
488 "Almond",
"Apple",
"Avocado",
"Cherry",
"Grape",
"Lemon",
489 "Olive",
"Orange",
"Peach",
"Pear",
"Plum",
"Walnut"
494 if uuids
is not None:
495 stomatal_wrapper.setBMFCoefficientsFromLibraryForUUIDs(self.
stomatal_model, species, uuids)
497 stomatal_wrapper.setBMFCoefficientsFromLibrary(self.
stomatal_model, species)
498 except Exception
as e:
499 error_msg = f
"Failed to set BMF coefficients from library for species '{species}': {e}"
500 if "species not found" in str(e).lower()
or "invalid species" in str(e).lower():
501 error_msg += f
"\nAvailable species: {', '.join(available_species)}"
505 def setDynamicTimeConstants(self, tau_open: float, tau_close: float, uuids: Optional[List[int]] =
None) ->
None:
507 Set time constants for dynamic stomatal opening and closing.
510 tau_open: Time constant (seconds) for stomatal opening
511 tau_close: Time constant (seconds) for stomatal closing
512 uuids: Optional list of primitive UUIDs. If None, applies to all primitives.
515 ValueError: If time constants are invalid
516 StomatalConductanceModelError: If operation fails
519 >>> # Set time constants for all leaves
520 >>> stomatal.setDynamicTimeConstants(tau_open=120.0, tau_close=240.0)
522 >>> # Set different time constants for specific leaves
523 >>> stomatal.setDynamicTimeConstants(tau_open=60.0, tau_close=180.0, uuids=[leaf1_uuid])
526 raise ValueError(
"Opening time constant must be positive")
528 raise ValueError(
"Closing time constant must be positive")
532 if uuids
is not None:
533 stomatal_wrapper.setDynamicTimeConstantsForUUIDs(self.
stomatal_model, tau_open, tau_close, uuids)
535 stomatal_wrapper.setDynamicTimeConstants(self.
stomatal_model, tau_open, tau_close)
536 except Exception
as e:
542 Add optional output primitive data to the Context.
545 label: Name of primitive data to output (e.g., "Ci", "gs", "E")
548 ValueError: If label is invalid
549 StomatalConductanceModelError: If operation fails
552 >>> # Output stomatal conductance values
553 >>> stomatal.optionalOutputPrimitiveData("gs")
555 >>> # Output intercellular CO2 concentration
556 >>> stomatal.optionalOutputPrimitiveData("Ci")
559 raise ValueError(
"Label cannot be empty")
563 stomatal_wrapper.optionalOutputPrimitiveData(self.
stomatal_model, label)
564 except Exception
as e:
569 Print a report detailing usage of default input values.
572 uuids: Optional list of primitive UUIDs. If None, reports on all primitives.
575 StomatalConductanceModelError: If operation fails
578 >>> # Print report for all primitives
579 >>> stomatal.printDefaultValueReport()
581 >>> # Print report for specific leaves
582 >>> stomatal.printDefaultValueReport(uuids=[leaf1_uuid, leaf2_uuid])
586 if uuids
is not None:
587 stomatal_wrapper.printDefaultValueReportForUUIDs(self.
stomatal_model, uuids)
590 except Exception
as e:
595 Check if StomatalConductanceModel is available in current build.
598 True if plugin is available, False otherwise
600 registry = get_plugin_registry()
601 return registry.is_plugin_available(
'stomatalconductance')
607 Create StomatalConductanceModel instance with context.
610 context: Helios Context
613 StomatalConductanceModel instance
Bailey model coefficients.
Ball-Berry-Leuning model coefficients.
Buckley-Mott-Farquhar model coefficients.
Ball-Woodrow-Berry model coefficients.
Exception raised for StomatalConductance-specific errors.
High-level interface for stomatal conductance modeling and gas exchange calculations.
bool is_available(self)
Check if StomatalConductanceModel is available in current build.
__del__(self)
Destructor to ensure C++ resources freed even without 'with' statement.
__enter__(self)
Context manager entry.
None setBMFCoefficients(self, BMFCoefficients coeffs, Optional[List[int]] uuids=None)
Set Buckley-Mott-Farquhar model coefficients.
None setMOPTCoefficients(self, MOPTCoefficients coeffs, Optional[List[int]] uuids=None)
Set Medlyn et al.
None printDefaultValueReport(self, Optional[List[int]] uuids=None)
Print a report detailing usage of default input values.
None setDynamicTimeConstants(self, float tau_open, float tau_close, Optional[List[int]] uuids=None)
Set time constants for dynamic stomatal opening and closing.
None enableMessages(self)
Enable console output messages from the stomatal conductance model.
None setBBLCoefficients(self, BBLCoefficients coeffs, Optional[List[int]] uuids=None)
Set Ball-Berry-Leuning model coefficients.
__init__(self, Context context)
Initialize StomatalConductanceModel with graceful plugin handling.
None setBWBCoefficients(self, BWBCoefficients coeffs, Optional[List[int]] uuids=None)
Set Ball-Woodrow-Berry model coefficients.
__exit__(self, exc_type, exc_value, traceback)
Context manager exit with proper cleanup.
getNativePtr(self)
Get the native pointer for advanced operations.
_check_context_alive(self)
Raise if the owning Context has been destroyed (see Context.check_context_alive).
None run(self, Optional[List[int]] uuids=None, Optional[float] dt=None)
Run the stomatal conductance model.
None optionalOutputPrimitiveData(self, str label)
Add optional output primitive data to the Context.
None setBMFCoefficientsFromLibrary(self, str species, Optional[List[int]] uuids=None)
Set BMF model coefficients using the built-in species library.
None disableMessages(self)
Disable console output messages from the stomatal conductance model.
None setBBCoefficients(self, BBCoefficients coeffs, Optional[List[int]] uuids=None)
Set Bailey model coefficients.
Exception classes for PyHelios library.
StomatalConductanceModel create_stomatal_conductance_model(Context context)
Create StomatalConductanceModel instance with context.