0.1.39
Loading...
Searching...
No Matches
StomatalConductance.py
Go to the documentation of this file.
1"""
2High-level StomatalConductance interface for PyHelios.
3
4This module provides a user-friendly interface to the stomatal conductance modeling
5capabilities with graceful plugin handling and informative error messages.
6"""
7
8import logging
9import math
10from typing import List, Optional, Union, NamedTuple
11from contextlib import contextmanager
12
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
17
18logger = logging.getLogger(__name__)
19
20
22 """Exception raised for StomatalConductance-specific errors."""
23 pass
24
25
26# Model Coefficient Classes for type safety and clarity
27class BWBCoefficients(NamedTuple):
28 """Ball-Woodrow-Berry model coefficients."""
29 gs0: float # mol/m²/s - minimum stomatal conductance
30 a1: float # dimensionless - sensitivity parameter
31
32
33class BBLCoefficients(NamedTuple):
34 """Ball-Berry-Leuning model coefficients."""
35 gs0: float # mol/m²/s - minimum stomatal conductance
36 a1: float # dimensionless - sensitivity parameter
37 D0: float # mmol/mol - VPD parameter
38
39
40class MOPTCoefficients(NamedTuple):
41 """Medlyn et al. optimality model coefficients."""
42 gs0: float # mol/m²/s - minimum stomatal conductance
43 g1: float # (kPa)^0.5 - marginal water use efficiency
44
45
46class BMFCoefficients(NamedTuple):
47 """Buckley-Mott-Farquhar model coefficients."""
48 Em: float # mmol/m²/s - maximum transpiration rate
49 i0: float # μmol/m²/s - minimum radiation
50 k: float # μmol/m²/s·mmol/mol - light response parameter
51 b: float # mmol/mol - humidity response parameter
52
53
54class BBCoefficients(NamedTuple):
55 """Bailey model coefficients."""
56 pi_0: float # MPa - turgor pressure at full closure
57 pi_m: float # MPa - turgor pressure at full opening
58 theta: float # μmol/m²/s - light saturation parameter
59 sigma: float # dimensionless - shape parameter
60 chi: float # mol/m²/s/MPa - hydraulic conductance parameter
61
62
64 """
65 High-level interface for stomatal conductance modeling and gas exchange calculations.
66
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.
70
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
77
78 The plugin includes a species library with pre-calibrated coefficients for
79 common plant species (Almond, Apple, Avocado, Grape, Lemon, Olive, Walnut, etc.).
80
81 Both steady-state and dynamic (time-stepping) calculations are supported,
82 with configurable time constants for stomatal opening and closing dynamics.
83
84 System requirements:
85 - Cross-platform support (Windows, Linux, macOS)
86 - No GPU required
87 - No special dependencies
88 - Stomatal conductance plugin compiled into PyHelios
89
90 Example:
91 >>> with Context() as context:
92 ... # Add leaf geometry
93 ... leaf_uuid = context.addPatch(center=[0, 0, 1], size=[0.1, 0.1])
94 ...
95 ... with StomatalConductanceModel(context) as stomatal:
96 ... # Set model coefficients using species library
97 ... stomatal.setBMFCoefficientsFromLibrary("Almond")
98 ...
99 ... # Run steady-state calculation
100 ... stomatal.run()
101 ...
102 ... # Or run dynamic simulation with timestep
103 ... stomatal.run(dt=60.0) # 60 second timestep
104 ...
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])
108 """
109
110 def __init__(self, context: Context):
111 """
112 Initialize StomatalConductanceModel with graceful plugin handling.
113
114 Args:
115 context: Helios Context instance
116
117 Raises:
118 TypeError: If context is not a Context instance
119 StomatalConductanceModelError: If stomatal conductance plugin is not available
120 """
121 # Validate context type - use duck typing to handle import state issues during testing
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__}")
126
127 self.context = context
128 self.stomatal_model = None
129
130 # Check plugin availability using registry
131 registry = get_plugin_registry()
132
133 if not registry.is_plugin_available('stomatalconductance'):
134 # Get helpful information about the missing plugin
135 plugin_info = registry.get_plugin_capabilities()
136 available_plugins = registry.get_available_plugins()
137
138 error_msg = (
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"
146 "Features:\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}"
156 )
157
158 # Suggest alternatives if available
159 alternatives = registry.suggest_alternatives('stomatalconductance')
160 if alternatives:
161 error_msg += f"\n\nAlternative plugins available: {alternatives}"
162 error_msg += "\nConsider using photosynthesis or energybalance for related plant physiology modeling."
163
164 raise StomatalConductanceModelError(error_msg)
165
166 # Plugin is available - create stomatal conductance model
167 try:
168 self.stomatal_model = stomatal_wrapper.createStomatalConductanceModel(context.getNativePtr())
169 if self.stomatal_model is None:
171 "Failed to create StomatalConductanceModel instance. "
172 "This may indicate a problem with the native library."
173 )
174 logger.info("StomatalConductanceModel created successfully")
175
176 except Exception as e:
177 raise StomatalConductanceModelError(f"Failed to initialize StomatalConductanceModel: {e}")
178
179 def _check_context_alive(self):
180 """Raise if the owning Context has been destroyed (see Context.check_context_alive)."""
181 check_context_alive(getattr(self, "context", None), "StomatalConductanceModel")
183 def __enter__(self):
184 """Context manager entry."""
185 return self
187 def __exit__(self, exc_type, exc_value, traceback):
188 """Context manager exit with proper cleanup."""
189 if self.stomatal_model is not None:
190 try:
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}")
195 finally:
196 self.stomatal_model = None
197
198 def __del__(self):
199 """Destructor to ensure C++ resources freed even without 'with' statement."""
200 if hasattr(self, 'stomatal_model') and self.stomatal_model is not None:
201 try:
202 stomatal_wrapper.destroyStomatalConductanceModel(self.stomatal_model)
203 self.stomatal_model = None
204 except Exception as e:
205 import warnings
206 warnings.warn(f"Error in StomatalConductanceModel.__del__: {e}")
207
208 def getNativePtr(self):
209 """Get the native pointer for advanced operations."""
210 return self.stomatal_model
212 def enableMessages(self) -> None:
213 """
214 Enable console output messages from the stomatal conductance model.
215
216 Raises:
217 StomatalConductanceModelError: If operation fails
218 """
220 try:
221 stomatal_wrapper.enableMessages(self.stomatal_model)
222 except Exception as e:
223 raise StomatalConductanceModelError(f"Failed to enable messages: {e}")
224
225 def disableMessages(self) -> None:
226 """
227 Disable console output messages from the stomatal conductance model.
228
229 Raises:
230 StomatalConductanceModelError: If operation fails
231 """
233 try:
234 stomatal_wrapper.disableMessages(self.stomatal_model)
235 except Exception as e:
236 raise StomatalConductanceModelError(f"Failed to disable messages: {e}")
237
238 def run(self, uuids: Optional[List[int]] = None, dt: Optional[float] = None) -> None:
239 """
240 Run the stomatal conductance model.
241
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)
247
248 Args:
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.
251
252 Raises:
253 ValueError: If parameters are invalid
254 StomatalConductanceModelError: If calculation fails
255
256 Example:
257 >>> # Steady state for all primitives
258 >>> stomatal.run()
259
260 >>> # Dynamic simulation with 60-second timestep
261 >>> stomatal.run(dt=60.0)
262
263 >>> # Steady state for specific leaves
264 >>> stomatal.run(uuids=[leaf1_uuid, leaf2_uuid])
265
266 >>> # Dynamic simulation for specific leaves
267 >>> stomatal.run(uuids=[leaf1_uuid, leaf2_uuid], dt=30.0)
268 """
270 try:
271 if dt is not None and uuids is not None:
272 # Dynamic simulation for specific UUIDs
273 stomatal_wrapper.runForUUIDsDynamic(self.stomatal_model, uuids, dt)
274 elif dt is not None:
275 # Dynamic simulation for all primitives
276 stomatal_wrapper.runDynamic(self.stomatal_model, dt)
277 elif uuids is not None:
278 # Steady state for specific UUIDs
279 stomatal_wrapper.runForUUIDs(self.stomatal_model, uuids)
280 else:
281 # Steady state for all primitives
282 stomatal_wrapper.run(self.stomatal_model)
283
284 except Exception as e:
285 raise StomatalConductanceModelError(f"Failed to run stomatal conductance model: {e}")
286
287 # Coefficient target resolution shared by all coefficient setters
288 @staticmethod
289 def _validate_target(method: str, uuids, material_label) -> None:
290 if uuids is not None and material_label is not None:
291 raise ValueError(f"{method}: pass either uuids or material_label, not both.")
292 if uuids is not None:
293 if isinstance(uuids, (str, bytes)) or not isinstance(uuids, (list, tuple)):
294 hint = " (to target a material, pass material_label=...)" if isinstance(uuids, str) else ""
295 raise ValueError(f"{method}: uuids must be a list of int UUIDs, got {type(uuids).__name__}{hint}")
296 if not all(isinstance(u, int) and not isinstance(u, bool) for u in uuids):
297 raise ValueError(f"{method}: uuids must contain only int UUIDs")
298 if material_label is not None and (not isinstance(material_label, str) or not material_label):
299 raise ValueError(f"{method}: material_label must be a non-empty str, got {type(material_label).__name__}")
300
301 @staticmethod
302 def _validate_bmf(coeffs: "BMFCoefficients") -> None:
303 if coeffs.Em <= 0.0:
304 raise ValueError("Em must be positive")
305 if coeffs.i0 < 0.0:
306 raise ValueError("i0 must be non-negative")
307 if coeffs.k <= 0.0:
308 raise ValueError("k must be positive")
309 if coeffs.b < 0.0:
310 raise ValueError("b must be non-negative")
311
312 # BWB Model Methods
313 def setBWBCoefficients(self, coeffs: BWBCoefficients, uuids: Optional[List[int]] = None,
314 material_label: Optional[str] = None) -> None:
315 """
316 Set Ball-Woodrow-Berry model coefficients.
317
318 Args:
319 coeffs: BWB model coefficients (gs0, a1)
320 uuids: Optional list of primitive UUIDs. If None and ``material_label`` is
321 also None, applies to all primitives.
322 material_label: Optional Context material label. The coefficients are stored on
323 the material and apply to every primitive that references it when run() is
324 called. Mutually exclusive with ``uuids``. See "Coefficient precedence" in
325 the Stomatal Conductance plugin documentation.
326
327 Raises:
328 ValueError: If coefficients are invalid, or both ``uuids`` and ``material_label`` are given
329 StomatalConductanceModelError: If operation fails (e.g. the material does not exist)
330
331 Example:
332 >>> bwb_coeffs = BWBCoefficients(gs0=0.0733, a1=9.422)
333 >>> stomatal.setBWBCoefficients(bwb_coeffs)
334 >>> stomatal.setBWBCoefficients(bwb_coeffs, material_label="sun_leaf")
335 """
336 if not isinstance(coeffs, BWBCoefficients):
337 raise ValueError(f"coeffs must be a BWBCoefficients instance, got {type(coeffs).__name__}")
338 self._validate_target("setBWBCoefficients", uuids, material_label)
339 if coeffs.gs0 < 0.0:
340 raise ValueError("gs0 must be non-negative")
341 if coeffs.a1 < 0.0:
342 raise ValueError("a1 must be non-negative")
343
345 try:
346 if material_label is not None:
347 stomatal_wrapper.setBWBCoefficientsForMaterial(self.stomatal_model, material_label, coeffs.gs0, coeffs.a1)
348 elif uuids is not None:
349 stomatal_wrapper.setBWBCoefficientsForUUIDs(self.stomatal_model, coeffs.gs0, coeffs.a1, uuids)
350 else:
351 stomatal_wrapper.setBWBCoefficients(self.stomatal_model, coeffs.gs0, coeffs.a1)
352 except Exception as e:
353 raise StomatalConductanceModelError(f"Failed to set BWB coefficients: {e}")
354
355 # BBL Model Methods
356 def setBBLCoefficients(self, coeffs: BBLCoefficients, uuids: Optional[List[int]] = None,
357 material_label: Optional[str] = None) -> None:
358 """
359 Set Ball-Berry-Leuning model coefficients.
360
361 Args:
362 coeffs: BBL model coefficients (gs0, a1, D0)
363 uuids: Optional list of primitive UUIDs. If None and ``material_label`` is
364 also None, applies to all primitives.
365 material_label: Optional Context material label; applies to every primitive that
366 references the material at run() time. Mutually exclusive with ``uuids``.
367
368 Raises:
369 ValueError: If coefficients are invalid, or both ``uuids`` and ``material_label`` are given
370 StomatalConductanceModelError: If operation fails
371
372 Example:
373 >>> bbl_coeffs = BBLCoefficients(gs0=0.0743, a1=4.265, D0=14570.0)
374 >>> stomatal.setBBLCoefficients(bbl_coeffs)
375 """
376 if not isinstance(coeffs, BBLCoefficients):
377 raise ValueError(f"coeffs must be a BBLCoefficients instance, got {type(coeffs).__name__}")
378 self._validate_target("setBBLCoefficients", uuids, material_label)
379 if coeffs.gs0 < 0.0:
380 raise ValueError("gs0 must be non-negative")
381 if coeffs.a1 < 0.0:
382 raise ValueError("a1 must be non-negative")
383 if coeffs.D0 <= 0.0:
384 raise ValueError("D0 must be positive")
385
387 try:
388 if material_label is not None:
389 stomatal_wrapper.setBBLCoefficientsForMaterial(self.stomatal_model, material_label, coeffs.gs0, coeffs.a1, coeffs.D0)
390 elif uuids is not None:
391 stomatal_wrapper.setBBLCoefficientsForUUIDs(self.stomatal_model, coeffs.gs0, coeffs.a1, coeffs.D0, uuids)
392 else:
393 stomatal_wrapper.setBBLCoefficients(self.stomatal_model, coeffs.gs0, coeffs.a1, coeffs.D0)
394 except Exception as e:
395 raise StomatalConductanceModelError(f"Failed to set BBL coefficients: {e}")
396
397 # MOPT Model Methods
398 def setMOPTCoefficients(self, coeffs: MOPTCoefficients, uuids: Optional[List[int]] = None,
399 material_label: Optional[str] = None) -> None:
400 """
401 Set Medlyn et al. optimality model coefficients.
402
403 Args:
404 coeffs: MOPT model coefficients (gs0, g1)
405 uuids: Optional list of primitive UUIDs. If None and ``material_label`` is
406 also None, applies to all primitives.
407 material_label: Optional Context material label; applies to every primitive that
408 references the material at run() time. Mutually exclusive with ``uuids``.
409
410 Raises:
411 ValueError: If coefficients are invalid, or both ``uuids`` and ``material_label`` are given
412 StomatalConductanceModelError: If operation fails
413
414 Example:
415 >>> mopt_coeffs = MOPTCoefficients(gs0=0.0825, g1=2.637)
416 >>> stomatal.setMOPTCoefficients(mopt_coeffs)
417 """
418 if not isinstance(coeffs, MOPTCoefficients):
419 raise ValueError(f"coeffs must be a MOPTCoefficients instance, got {type(coeffs).__name__}")
420 self._validate_target("setMOPTCoefficients", uuids, material_label)
421 if coeffs.gs0 < 0.0:
422 raise ValueError("gs0 must be non-negative")
423 if coeffs.g1 <= 0.0:
424 raise ValueError("g1 must be positive")
425
427 try:
428 if material_label is not None:
429 stomatal_wrapper.setMOPTCoefficientsForMaterial(self.stomatal_model, material_label, coeffs.gs0, coeffs.g1)
430 elif uuids is not None:
431 stomatal_wrapper.setMOPTCoefficientsForUUIDs(self.stomatal_model, coeffs.gs0, coeffs.g1, uuids)
432 else:
433 stomatal_wrapper.setMOPTCoefficients(self.stomatal_model, coeffs.gs0, coeffs.g1)
434 except Exception as e:
435 raise StomatalConductanceModelError(f"Failed to set MOPT coefficients: {e}")
436
437 # BMF Model Methods
438 def setBMFCoefficients(self, coeffs: Union[BMFCoefficients, List[BMFCoefficients]],
439 uuids: Optional[List[int]] = None,
440 material_label: Optional[str] = None) -> None:
441 """
442 Set Buckley-Mott-Farquhar model coefficients.
443
444 ``coeffs`` may be a single :class:`BMFCoefficients` (applied to all primitives, to
445 ``uuids``, or to ``material_label``), or a list of :class:`BMFCoefficients` with one
446 entry per UUID in ``uuids`` (``coeffs[i]`` is applied to ``uuids[i]``).
447
448 Args:
449 coeffs: BMF model coefficients (Em, i0, k, b), or a list of them (per-UUID form)
450 uuids: Optional list of primitive UUIDs. Required for the per-UUID list form.
451 If None and ``material_label`` is also None, applies to all primitives.
452 material_label: Optional Context material label; applies to every primitive that
453 references the material at run() time. Mutually exclusive with ``uuids`` and
454 not accepted with the per-UUID list form.
455
456 Raises:
457 ValueError: If coefficients are invalid, list and UUID lengths differ, or both
458 ``uuids`` and ``material_label`` are given
459 StomatalConductanceModelError: If operation fails
460
461 Example:
462 >>> bmf_coeffs = BMFCoefficients(Em=258.25, i0=38.65, k=232916.82, b=609.67)
463 >>> stomatal.setBMFCoefficients(bmf_coeffs)
464 >>> # One coefficient set per leaf
465 >>> stomatal.setBMFCoefficients([coeffs_a, coeffs_b], uuids=[leaf_a, leaf_b])
466 """
467 self._validate_target("setBMFCoefficients", uuids, material_label)
468 if isinstance(coeffs, BMFCoefficients):
469 self._validate_bmf(coeffs)
470 per_uuid = False
471 elif isinstance(coeffs, (list, tuple)) and coeffs and all(isinstance(c, BMFCoefficients) for c in coeffs):
472 if material_label is not None:
473 raise ValueError("setBMFCoefficients: a list of coefficient sets cannot be combined with material_label; "
474 "pass a single BMFCoefficients instead.")
475 if uuids is None:
476 raise ValueError("setBMFCoefficients: a list of coefficient sets requires uuids (one UUID per coefficient set).")
477 if len(coeffs) != len(uuids):
478 raise ValueError(f"setBMFCoefficients: number of coefficient sets ({len(coeffs)}) does not match "
479 f"number of UUIDs ({len(uuids)}).")
480 for c in coeffs:
481 self._validate_bmf(c)
482 per_uuid = True
483 else:
484 raise ValueError(f"coeffs must be a BMFCoefficients instance or a non-empty list of BMFCoefficients, "
485 f"got {type(coeffs).__name__}")
486
488 try:
489 if per_uuid:
490 stomatal_wrapper.setBMFCoefficientsPerUUID(self.stomatal_model, [list(c) for c in coeffs], list(uuids))
491 elif material_label is not None:
492 stomatal_wrapper.setBMFCoefficientsForMaterial(self.stomatal_model, material_label, coeffs.Em, coeffs.i0, coeffs.k, coeffs.b)
493 elif uuids is not None:
494 stomatal_wrapper.setBMFCoefficientsForUUIDs(self.stomatal_model, coeffs.Em, coeffs.i0, coeffs.k, coeffs.b, uuids)
495 else:
496 stomatal_wrapper.setBMFCoefficients(self.stomatal_model, coeffs.Em, coeffs.i0, coeffs.k, coeffs.b)
497 except Exception as e:
498 raise StomatalConductanceModelError(f"Failed to set BMF coefficients: {e}")
499
500 # BB Model Methods
501 def setBBCoefficients(self, coeffs: BBCoefficients, uuids: Optional[List[int]] = None,
502 material_label: Optional[str] = None) -> None:
503 """
504 Set Bailey model coefficients.
505
506 Args:
507 coeffs: BB model coefficients (pi_0, pi_m, theta, sigma, chi)
508 uuids: Optional list of primitive UUIDs. If None and ``material_label`` is
509 also None, applies to all primitives.
510 material_label: Optional Context material label; applies to every primitive that
511 references the material at run() time. Mutually exclusive with ``uuids``.
512
513 Raises:
514 ValueError: If coefficients are invalid, or both ``uuids`` and ``material_label`` are given
515 StomatalConductanceModelError: If operation fails
516
517 Example:
518 >>> bb_coeffs = BBCoefficients(pi_0=1.0, pi_m=1.67, theta=211.22, sigma=0.4408, chi=2.076)
519 >>> stomatal.setBBCoefficients(bb_coeffs)
520 """
521 if not isinstance(coeffs, BBCoefficients):
522 raise ValueError(f"coeffs must be a BBCoefficients instance, got {type(coeffs).__name__}")
523 self._validate_target("setBBCoefficients", uuids, material_label)
524 if coeffs.pi_0 <= 0.0:
525 raise ValueError("pi_0 must be positive")
526 if coeffs.pi_m <= 0.0:
527 raise ValueError("pi_m must be positive")
528 if coeffs.theta <= 0.0:
529 raise ValueError("theta must be positive")
530 if coeffs.sigma <= 0.0:
531 raise ValueError("sigma must be positive")
532 if coeffs.chi <= 0.0:
533 raise ValueError("chi must be positive")
534
536 try:
537 if material_label is not None:
538 stomatal_wrapper.setBBCoefficientsForMaterial(self.stomatal_model, material_label, coeffs.pi_0, coeffs.pi_m, coeffs.theta, coeffs.sigma, coeffs.chi)
539 elif uuids is not None:
540 stomatal_wrapper.setBBCoefficientsForUUIDs(self.stomatal_model, coeffs.pi_0, coeffs.pi_m, coeffs.theta, coeffs.sigma, coeffs.chi, uuids)
541 else:
542 stomatal_wrapper.setBBCoefficients(self.stomatal_model, coeffs.pi_0, coeffs.pi_m, coeffs.theta, coeffs.sigma, coeffs.chi)
543 except Exception as e:
544 raise StomatalConductanceModelError(f"Failed to set BB coefficients: {e}")
545
546 # Species Library Methods
547 def setBMFCoefficientsFromLibrary(self, species: str, uuids: Optional[List[int]] = None,
548 material_label: Optional[str] = None) -> None:
549 """
550 Set BMF model coefficients using the built-in species library.
551
552 Args:
553 species: Species name from the library (e.g., "Almond", "Apple", "Grape", "Walnut").
554 An unrecognized name raises, and the error message lists the valid names.
555 uuids: Optional list of primitive UUIDs. If None and ``material_label`` is
556 also None, applies to all primitives.
557 material_label: Optional Context material label; applies to every primitive that
558 references the material at run() time. Mutually exclusive with ``uuids``.
559
560 Raises:
561 ValueError: If species name is not a non-empty str, or both ``uuids`` and ``material_label`` are given
562 StomatalConductanceModelError: If the species is not in the library, or the operation fails
563
564 Example:
565 >>> # Set coefficients for almond tree
566 >>> stomatal.setBMFCoefficientsFromLibrary("Almond")
567
568 >>> # Set coefficients for specific leaves only
569 >>> stomatal.setBMFCoefficientsFromLibrary("Grape", uuids=[leaf1_uuid, leaf2_uuid])
570
571 >>> # Set coefficients for every primitive using a material
572 >>> stomatal.setBMFCoefficientsFromLibrary("Walnut", material_label="walnut_leaf")
573 """
574 if not isinstance(species, str) or not species:
575 raise ValueError("Species name must be a non-empty str")
576 self._validate_target("setBMFCoefficientsFromLibrary", uuids, material_label)
577
579 try:
580 if material_label is not None:
581 stomatal_wrapper.setBMFCoefficientsFromLibraryForMaterial(self.stomatal_model, species, material_label)
582 elif uuids is not None:
583 stomatal_wrapper.setBMFCoefficientsFromLibraryForUUIDs(self.stomatal_model, species, uuids)
584 else:
585 stomatal_wrapper.setBMFCoefficientsFromLibrary(self.stomatal_model, species)
586 except Exception as e:
587 raise StomatalConductanceModelError(f"Failed to set BMF coefficients from library for species '{species}': {e}")
588
589 def getBMFCoefficientsFromLibrary(self, species: str) -> BMFCoefficients:
590 """
591 Return the library BMF coefficients for a species without applying them.
592
593 The result can be modified and passed to :meth:`setBMFCoefficients`.
594
595 Args:
596 species: Species name from the library (e.g., "Almond", "Walnut"). An unrecognized
597 name raises, and the error message lists the valid names.
598
599 Returns:
600 BMFCoefficients for the species
601
602 Raises:
603 ValueError: If species name is empty or not a str
604 StomatalConductanceModelError: If the species is not in the library, or the operation fails
605
606 Example:
607 >>> almond = stomatal.getBMFCoefficientsFromLibrary("Almond")
608 >>> stomatal.setBMFCoefficients(almond._replace(Em=almond.Em * 0.8), uuids=[leaf])
609 """
610 if not isinstance(species, str) or not species:
611 raise ValueError("Species name must be a non-empty str")
613 try:
614 values = stomatal_wrapper.getBMFCoefficientsFromLibrary(self.stomatal_model, species)
615 except Exception as e:
616 raise StomatalConductanceModelError(f"Failed to get BMF coefficients from library for species '{species}': {e}")
617 return BMFCoefficients(Em=values[0], i0=values[1], k=values[2], b=values[3])
618
619 # Dynamic Time Constants
620 def setDynamicTimeConstants(self, tau_open: float, tau_close: float, uuids: Optional[List[int]] = None) -> None:
621 """
622 Set time constants for dynamic stomatal opening and closing.
623
624 Args:
625 tau_open: Time constant (seconds) for stomatal opening
626 tau_close: Time constant (seconds) for stomatal closing
627 uuids: Optional list of primitive UUIDs. If None, applies to all primitives.
628
629 Raises:
630 ValueError: If time constants are invalid
631 StomatalConductanceModelError: If operation fails
632
633 Example:
634 >>> # Set time constants for all leaves
635 >>> stomatal.setDynamicTimeConstants(tau_open=120.0, tau_close=240.0)
636
637 >>> # Set different time constants for specific leaves
638 >>> stomatal.setDynamicTimeConstants(tau_open=60.0, tau_close=180.0, uuids=[leaf1_uuid])
639 """
640 # tau appears in the denominator of the forward Euler update, so zero gives a
641 # non-finite conductance and a negative value inverts the relaxation so that stomata
642 # diverge away from the steady-state value. Mirrors validateDynamicTimeConstants
643 # added in helios-core 1.3.80.
644 if not math.isfinite(tau_open) or tau_open <= 0.0:
645 raise ValueError("Opening time constant must be finite and positive")
646 if not math.isfinite(tau_close) or tau_close <= 0.0:
647 raise ValueError("Closing time constant must be finite and positive")
648
650 try:
651 if uuids is not None:
652 stomatal_wrapper.setDynamicTimeConstantsForUUIDs(self.stomatal_model, tau_open, tau_close, uuids)
653 else:
654 stomatal_wrapper.setDynamicTimeConstants(self.stomatal_model, tau_open, tau_close)
655 except Exception as e:
656 raise StomatalConductanceModelError(f"Failed to set dynamic time constants: {e}")
657
658 # Utility Methods
659 def optionalOutputPrimitiveData(self, label: str) -> None:
660 """
661 Add optional output primitive data to the Context.
662
663 Args:
664 label: Name of primitive data to output (e.g., "Ci", "gs", "E")
665
666 Raises:
667 ValueError: If label is invalid
668 StomatalConductanceModelError: If operation fails
669
670 Example:
671 >>> # Output stomatal conductance values
672 >>> stomatal.optionalOutputPrimitiveData("gs")
673
674 >>> # Output intercellular CO2 concentration
675 >>> stomatal.optionalOutputPrimitiveData("Ci")
676 """
677 if not label:
678 raise ValueError("Label cannot be empty")
679
681 try:
682 stomatal_wrapper.optionalOutputPrimitiveData(self.stomatal_model, label)
683 except Exception as e:
684 raise StomatalConductanceModelError(f"Failed to add optional output data '{label}': {e}")
685
686 def printDefaultValueReport(self, uuids: Optional[List[int]] = None) -> None:
687 """
688 Print a report detailing usage of default input values.
689
690 Args:
691 uuids: Optional list of primitive UUIDs. If None, reports on all primitives.
692
693 Raises:
694 StomatalConductanceModelError: If operation fails
695
696 Example:
697 >>> # Print report for all primitives
698 >>> stomatal.printDefaultValueReport()
699
700 >>> # Print report for specific leaves
701 >>> stomatal.printDefaultValueReport(uuids=[leaf1_uuid, leaf2_uuid])
702 """
704 try:
705 if uuids is not None:
706 stomatal_wrapper.printDefaultValueReportForUUIDs(self.stomatal_model, uuids)
707 else:
708 stomatal_wrapper.printDefaultValueReport(self.stomatal_model)
709 except Exception as e:
710 raise StomatalConductanceModelError(f"Failed to print default value report: {e}")
711
712 def is_available(self) -> bool:
713 """
714 Check if StomatalConductanceModel is available in current build.
715
716 Returns:
717 True if plugin is available, False otherwise
718 """
719 registry = get_plugin_registry()
720 return registry.is_plugin_available('stomatalconductance')
721
722
723# Convenience function
724def create_stomatal_conductance_model(context: Context) -> StomatalConductanceModel:
725 """
726 Create StomatalConductanceModel instance with context.
727
728 Args:
729 context: Helios Context
730
731 Returns:
732 StomatalConductanceModel instance
733 """
734 return StomatalConductanceModel(context)
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.
None setBMFCoefficients(self, Union[BMFCoefficients, List[BMFCoefficients]] coeffs, Optional[List[int]] uuids=None, Optional[str] material_label=None)
Set Buckley-Mott-Farquhar model coefficients.
BMFCoefficients getBMFCoefficientsFromLibrary(self, str species)
Return the library BMF coefficients for a species without applying them.
None printDefaultValueReport(self, Optional[List[int]] uuids=None)
Print a report detailing usage of default input values.
None setBMFCoefficientsFromLibrary(self, str species, Optional[List[int]] uuids=None, Optional[str] material_label=None)
Set BMF model coefficients using the built-in species library.
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 setBWBCoefficients(self, BWBCoefficients coeffs, Optional[List[int]] uuids=None, Optional[str] material_label=None)
Set Ball-Woodrow-Berry model coefficients.
None setMOPTCoefficients(self, MOPTCoefficients coeffs, Optional[List[int]] uuids=None, Optional[str] material_label=None)
Set Medlyn et al.
None setBBCoefficients(self, BBCoefficients coeffs, Optional[List[int]] uuids=None, Optional[str] material_label=None)
Set Bailey model coefficients.
__init__(self, Context context)
Initialize StomatalConductanceModel with graceful plugin handling.
__exit__(self, exc_type, exc_value, traceback)
Context manager exit with proper cleanup.
None setBBLCoefficients(self, BBLCoefficients coeffs, Optional[List[int]] uuids=None, Optional[str] material_label=None)
Set Ball-Berry-Leuning model coefficients.
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 _validate_target(str method, uuids, material_label)
None disableMessages(self)
Disable console output messages from the stomatal conductance model.
Exception classes for PyHelios library.
Definition exceptions.py:10
StomatalConductanceModel create_stomatal_conductance_model(Context context)
Create StomatalConductanceModel instance with context.