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.
10from typing
import List, Optional, Union, NamedTuple
11from contextlib
import contextmanager
13from .plugins.registry
import get_plugin_registry
14from .wrappers
import UStomatalConductanceWrapper
as stomatal_wrapper
15from .Context
import Context, check_context_alive
16from .exceptions
import HeliosError
18logger = logging.getLogger(__name__)
22 """Exception raised for StomatalConductance-specific errors."""
28 """Ball-Woodrow-Berry model coefficients."""
34 """Ball-Berry-Leuning model coefficients."""
41 """Medlyn et al. optimality model coefficients."""
47 """Buckley-Mott-Farquhar model coefficients."""
55 """Bailey model coefficients."""
65 High-level interface for stomatal conductance modeling and gas exchange calculations.
67 This class provides a user-friendly wrapper around the native Helios
68 stomatal conductance plugin with automatic plugin availability checking and
69 graceful error handling.
71 The stomatal conductance model implements five different stomatal response models:
72 - BWB: Ball, Woodrow, Berry (1987) - original model
73 - BBL: Ball, Berry, Leuning (1990, 1995) - includes VPD response
74 - MOPT: Medlyn et al. (2011) - optimality-based model
75 - BMF: Buckley, Mott, Farquhar - simplified mechanistic model
76 - BB: Bailey - hydraulic-based model
78 The plugin includes a species library with pre-calibrated coefficients for
79 common plant species (Almond, Apple, Avocado, Grape, Lemon, Olive, Walnut, etc.).
81 Both steady-state and dynamic (time-stepping) calculations are supported,
82 with configurable time constants for stomatal opening and closing dynamics.
85 - Cross-platform support (Windows, Linux, macOS)
87 - No special dependencies
88 - Stomatal conductance plugin compiled into PyHelios
91 >>> with Context() as context:
92 ... # Add leaf geometry
93 ... leaf_uuid = context.addPatch(center=[0, 0, 1], size=[0.1, 0.1])
95 ... with StomatalConductanceModel(context) as stomatal:
96 ... # Set model coefficients using species library
97 ... stomatal.setBMFCoefficientsFromLibrary("Almond")
99 ... # Run steady-state calculation
102 ... # Or run dynamic simulation with timestep
103 ... stomatal.run(dt=60.0) # 60 second timestep
105 ... # Set custom BMF coefficients for specific leaves
106 ... bmf_coeffs = BMFCoefficients(Em=258.25, i0=38.65, k=232916.82, b=609.67)
107 ... stomatal.setBMFCoefficients(bmf_coeffs, uuids=[leaf_uuid])
110 def __init__(self, context: Context):
112 Initialize StomatalConductanceModel with graceful plugin handling.
115 context: Helios Context instance
118 TypeError: If context is not a Context instance
119 StomatalConductanceModelError: If stomatal conductance plugin is not available
122 if not (hasattr(context,
'__class__')
and
123 (isinstance(context, Context)
or
124 context.__class__.__name__ ==
'Context')):
125 raise TypeError(f
"StomatalConductanceModel requires a Context instance, got {type(context).__name__}")
131 registry = get_plugin_registry()
133 if not registry.is_plugin_available(
'stomatalconductance'):
135 plugin_info = registry.get_plugin_capabilities()
136 available_plugins = registry.get_available_plugins()
139 "StomatalConductanceModel requires the 'stomatalconductance' plugin which is not available.\n\n"
140 "The stomatal conductance plugin provides gas exchange calculations using five validated models:\n"
141 "- Ball-Woodrow-Berry (BWB) - classic stomatal response\n"
142 "- Ball-Berry-Leuning (BBL) - includes vapor pressure deficit\n"
143 "- Medlyn et al. optimality (MOPT) - optimal stomatal behavior\n"
144 "- Buckley-Mott-Farquhar (BMF) - mechanistic approach\n"
145 "- Bailey (BB) - hydraulic-based model\n\n"
147 "- Species library with pre-calibrated coefficients\n"
148 "- Dynamic time-stepping with configurable time constants\n"
149 "- No GPU or special dependencies required\n\n"
150 "To enable stomatal conductance modeling:\n"
151 "1. Build PyHelios with stomatal conductance plugin:\n"
152 " build_scripts/build_helios --plugins stomatalconductance\n"
153 "2. Or build with multiple plugins:\n"
154 " build_scripts/build_helios --plugins stomatalconductance,energybalance,photosynthesis\n"
155 f
"\nCurrently available plugins: {available_plugins}"
159 alternatives = registry.suggest_alternatives(
'stomatalconductance')
161 error_msg += f
"\n\nAlternative plugins available: {alternatives}"
162 error_msg +=
"\nConsider using photosynthesis or energybalance for related plant physiology modeling."
168 self.
stomatal_model = stomatal_wrapper.createStomatalConductanceModel(context.getNativePtr())
171 "Failed to create StomatalConductanceModel instance. "
172 "This may indicate a problem with the native library."
174 logger.info(
"StomatalConductanceModel created successfully")
176 except Exception
as e:
180 """Raise if the owning Context has been destroyed (see Context.check_context_alive)."""
181 check_context_alive(getattr(self,
"context",
None),
"StomatalConductanceModel")
184 """Context manager entry."""
187 def __exit__(self, exc_type, exc_value, traceback):
188 """Context manager exit with proper cleanup."""
191 stomatal_wrapper.destroyStomatalConductanceModel(self.
stomatal_model)
192 logger.debug(
"StomatalConductanceModel destroyed successfully")
193 except Exception
as e:
194 logger.warning(f
"Error destroying StomatalConductanceModel: {e}")
199 """Destructor to ensure C++ resources freed even without 'with' statement."""
200 if hasattr(self,
'stomatal_model')
and self.
stomatal_model is not None:
202 stomatal_wrapper.destroyStomatalConductanceModel(self.
stomatal_model)
204 except Exception
as e:
206 warnings.warn(f
"Error in StomatalConductanceModel.__del__: {e}")
209 """Get the native pointer for advanced operations."""
214 Enable console output messages from the stomatal conductance model.
217 StomatalConductanceModelError: If operation fails
222 except Exception
as e:
227 Disable console output messages from the stomatal conductance model.
230 StomatalConductanceModelError: If operation fails
235 except Exception
as e:
238 def run(self, uuids: Optional[List[int]] =
None, dt: Optional[float] =
None) ->
None:
240 Run the stomatal conductance model.
242 This method supports multiple execution modes:
243 - Steady state for all primitives: run()
244 - Dynamic with timestep for all primitives: run(dt=60.0)
245 - Steady state for specific primitives: run(uuids=[1, 2, 3])
246 - Dynamic with timestep for specific primitives: run(uuids=[1, 2, 3], dt=60.0)
249 uuids: Optional list of primitive UUIDs to process. If None, processes all primitives.
250 dt: Optional timestep in seconds for dynamic simulation. If None, runs steady-state.
253 ValueError: If parameters are invalid
254 StomatalConductanceModelError: If calculation fails
257 >>> # Steady state for all primitives
260 >>> # Dynamic simulation with 60-second timestep
261 >>> stomatal.run(dt=60.0)
263 >>> # Steady state for specific leaves
264 >>> stomatal.run(uuids=[leaf1_uuid, leaf2_uuid])
266 >>> # Dynamic simulation for specific leaves
267 >>> stomatal.run(uuids=[leaf1_uuid, leaf2_uuid], dt=30.0)
271 if dt
is not None and uuids
is not None:
273 stomatal_wrapper.runForUUIDsDynamic(self.
stomatal_model, uuids, dt)
277 elif uuids
is not None:
284 except Exception
as e:
288 def setBWBCoefficients(self, coeffs: BWBCoefficients, uuids: Optional[List[int]] =
None) ->
None:
290 Set Ball-Woodrow-Berry model coefficients.
293 coeffs: BWB model coefficients (gs0, a1)
294 uuids: Optional list of primitive UUIDs. If None, applies to all primitives.
297 ValueError: If coefficients are invalid
298 StomatalConductanceModelError: If operation fails
301 >>> bwb_coeffs = BWBCoefficients(gs0=0.0733, a1=9.422)
302 >>> stomatal.setBWBCoefficients(bwb_coeffs)
304 if not isinstance(coeffs, BWBCoefficients):
305 raise ValueError(
"coeffs must be a BWBCoefficients instance")
307 raise ValueError(
"gs0 must be non-negative")
309 raise ValueError(
"a1 must be non-negative")
313 if uuids
is not None:
314 stomatal_wrapper.setBWBCoefficientsForUUIDs(self.
stomatal_model, coeffs.gs0, coeffs.a1, uuids)
316 stomatal_wrapper.setBWBCoefficients(self.
stomatal_model, coeffs.gs0, coeffs.a1)
317 except Exception
as e:
321 def setBBLCoefficients(self, coeffs: BBLCoefficients, uuids: Optional[List[int]] =
None) ->
None:
323 Set Ball-Berry-Leuning model coefficients.
326 coeffs: BBL model coefficients (gs0, a1, D0)
327 uuids: Optional list of primitive UUIDs. If None, applies to all primitives.
330 ValueError: If coefficients are invalid
331 StomatalConductanceModelError: If operation fails
334 >>> bbl_coeffs = BBLCoefficients(gs0=0.0743, a1=4.265, D0=14570.0)
335 >>> stomatal.setBBLCoefficients(bbl_coeffs)
337 if not isinstance(coeffs, BBLCoefficients):
338 raise ValueError(
"coeffs must be a BBLCoefficients instance")
340 raise ValueError(
"gs0 must be non-negative")
342 raise ValueError(
"a1 must be non-negative")
344 raise ValueError(
"D0 must be positive")
348 if uuids
is not None:
349 stomatal_wrapper.setBBLCoefficientsForUUIDs(self.
stomatal_model, coeffs.gs0, coeffs.a1, coeffs.D0, uuids)
351 stomatal_wrapper.setBBLCoefficients(self.
stomatal_model, coeffs.gs0, coeffs.a1, coeffs.D0)
352 except Exception
as e:
356 def setMOPTCoefficients(self, coeffs: MOPTCoefficients, uuids: Optional[List[int]] =
None) ->
None:
358 Set Medlyn et al. optimality model coefficients.
361 coeffs: MOPT model coefficients (gs0, g1)
362 uuids: Optional list of primitive UUIDs. If None, applies to all primitives.
365 ValueError: If coefficients are invalid
366 StomatalConductanceModelError: If operation fails
369 >>> mopt_coeffs = MOPTCoefficients(gs0=0.0825, g1=2.637)
370 >>> stomatal.setMOPTCoefficients(mopt_coeffs)
372 if not isinstance(coeffs, MOPTCoefficients):
373 raise ValueError(
"coeffs must be a MOPTCoefficients instance")
375 raise ValueError(
"gs0 must be non-negative")
377 raise ValueError(
"g1 must be positive")
381 if uuids
is not None:
382 stomatal_wrapper.setMOPTCoefficientsForUUIDs(self.
stomatal_model, coeffs.gs0, coeffs.g1, uuids)
384 stomatal_wrapper.setMOPTCoefficients(self.
stomatal_model, coeffs.gs0, coeffs.g1)
385 except Exception
as e:
389 def setBMFCoefficients(self, coeffs: BMFCoefficients, uuids: Optional[List[int]] =
None) ->
None:
391 Set Buckley-Mott-Farquhar model coefficients.
394 coeffs: BMF model coefficients (Em, i0, k, b)
395 uuids: Optional list of primitive UUIDs. If None, applies to all primitives.
398 ValueError: If coefficients are invalid
399 StomatalConductanceModelError: If operation fails
402 >>> bmf_coeffs = BMFCoefficients(Em=258.25, i0=38.65, k=232916.82, b=609.67)
403 >>> stomatal.setBMFCoefficients(bmf_coeffs)
405 if not isinstance(coeffs, BMFCoefficients):
406 raise ValueError(
"coeffs must be a BMFCoefficients instance")
408 raise ValueError(
"Em must be positive")
410 raise ValueError(
"i0 must be non-negative")
412 raise ValueError(
"k must be positive")
414 raise ValueError(
"b must be positive")
418 if uuids
is not None:
419 stomatal_wrapper.setBMFCoefficientsForUUIDs(self.
stomatal_model, coeffs.Em, coeffs.i0, coeffs.k, coeffs.b, uuids)
421 stomatal_wrapper.setBMFCoefficients(self.
stomatal_model, coeffs.Em, coeffs.i0, coeffs.k, coeffs.b)
422 except Exception
as e:
426 def setBBCoefficients(self, coeffs: BBCoefficients, uuids: Optional[List[int]] =
None) ->
None:
428 Set Bailey model coefficients.
431 coeffs: BB model coefficients (pi_0, pi_m, theta, sigma, chi)
432 uuids: Optional list of primitive UUIDs. If None, applies to all primitives.
435 ValueError: If coefficients are invalid
436 StomatalConductanceModelError: If operation fails
439 >>> bb_coeffs = BBCoefficients(pi_0=1.0, pi_m=1.67, theta=211.22, sigma=0.4408, chi=2.076)
440 >>> stomatal.setBBCoefficients(bb_coeffs)
442 if not isinstance(coeffs, BBCoefficients):
443 raise ValueError(
"coeffs must be a BBCoefficients instance")
444 if coeffs.pi_0 <= 0.0:
445 raise ValueError(
"pi_0 must be positive")
446 if coeffs.pi_m <= 0.0:
447 raise ValueError(
"pi_m must be positive")
448 if coeffs.theta <= 0.0:
449 raise ValueError(
"theta must be positive")
450 if coeffs.sigma <= 0.0:
451 raise ValueError(
"sigma must be positive")
452 if coeffs.chi <= 0.0:
453 raise ValueError(
"chi must be positive")
457 if uuids
is not None:
458 stomatal_wrapper.setBBCoefficientsForUUIDs(self.
stomatal_model, coeffs.pi_0, coeffs.pi_m, coeffs.theta, coeffs.sigma, coeffs.chi, uuids)
460 stomatal_wrapper.setBBCoefficients(self.
stomatal_model, coeffs.pi_0, coeffs.pi_m, coeffs.theta, coeffs.sigma, coeffs.chi)
461 except Exception
as e:
467 Set BMF model coefficients using the built-in species library.
470 species: Species name from the library (e.g., "Almond", "Apple", "Grape", "Walnut")
471 uuids: Optional list of primitive UUIDs. If None, applies to all primitives.
474 ValueError: If species name is invalid
475 StomatalConductanceModelError: If operation fails
478 >>> # Set coefficients for almond tree
479 >>> stomatal.setBMFCoefficientsFromLibrary("Almond")
481 >>> # Set coefficients for specific leaves only
482 >>> stomatal.setBMFCoefficientsFromLibrary("Grape", uuids=[leaf1_uuid, leaf2_uuid])
485 raise ValueError(
"Species name cannot be empty")
488 available_species = [
489 "Almond",
"Apple",
"Avocado",
"Cherry",
"Grape",
"Lemon",
490 "Olive",
"Orange",
"Peach",
"Pear",
"Plum",
"Walnut"
495 if uuids
is not None:
496 stomatal_wrapper.setBMFCoefficientsFromLibraryForUUIDs(self.
stomatal_model, species, uuids)
498 stomatal_wrapper.setBMFCoefficientsFromLibrary(self.
stomatal_model, species)
499 except Exception
as e:
500 error_msg = f
"Failed to set BMF coefficients from library for species '{species}': {e}"
501 if "species not found" in str(e).lower()
or "invalid species" in str(e).lower():
502 error_msg += f
"\nAvailable species: {', '.join(available_species)}"
506 def setDynamicTimeConstants(self, tau_open: float, tau_close: float, uuids: Optional[List[int]] =
None) ->
None:
508 Set time constants for dynamic stomatal opening and closing.
511 tau_open: Time constant (seconds) for stomatal opening
512 tau_close: Time constant (seconds) for stomatal closing
513 uuids: Optional list of primitive UUIDs. If None, applies to all primitives.
516 ValueError: If time constants are invalid
517 StomatalConductanceModelError: If operation fails
520 >>> # Set time constants for all leaves
521 >>> stomatal.setDynamicTimeConstants(tau_open=120.0, tau_close=240.0)
523 >>> # Set different time constants for specific leaves
524 >>> stomatal.setDynamicTimeConstants(tau_open=60.0, tau_close=180.0, uuids=[leaf1_uuid])
530 if not math.isfinite(tau_open)
or tau_open <= 0.0:
531 raise ValueError(
"Opening time constant must be finite and positive")
532 if not math.isfinite(tau_close)
or tau_close <= 0.0:
533 raise ValueError(
"Closing time constant must be finite and positive")
537 if uuids
is not None:
538 stomatal_wrapper.setDynamicTimeConstantsForUUIDs(self.
stomatal_model, tau_open, tau_close, uuids)
540 stomatal_wrapper.setDynamicTimeConstants(self.
stomatal_model, tau_open, tau_close)
541 except Exception
as e:
547 Add optional output primitive data to the Context.
550 label: Name of primitive data to output (e.g., "Ci", "gs", "E")
553 ValueError: If label is invalid
554 StomatalConductanceModelError: If operation fails
557 >>> # Output stomatal conductance values
558 >>> stomatal.optionalOutputPrimitiveData("gs")
560 >>> # Output intercellular CO2 concentration
561 >>> stomatal.optionalOutputPrimitiveData("Ci")
564 raise ValueError(
"Label cannot be empty")
568 stomatal_wrapper.optionalOutputPrimitiveData(self.
stomatal_model, label)
569 except Exception
as e:
574 Print a report detailing usage of default input values.
577 uuids: Optional list of primitive UUIDs. If None, reports on all primitives.
580 StomatalConductanceModelError: If operation fails
583 >>> # Print report for all primitives
584 >>> stomatal.printDefaultValueReport()
586 >>> # Print report for specific leaves
587 >>> stomatal.printDefaultValueReport(uuids=[leaf1_uuid, leaf2_uuid])
591 if uuids
is not None:
592 stomatal_wrapper.printDefaultValueReportForUUIDs(self.
stomatal_model, uuids)
595 except Exception
as e:
600 Check if StomatalConductanceModel is available in current build.
603 True if plugin is available, False otherwise
605 registry = get_plugin_registry()
606 return registry.is_plugin_available(
'stomatalconductance')
612 Create StomatalConductanceModel instance with context.
615 context: Helios Context
618 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.