0.1.33
Loading...
Searching...
No Matches
EnergyBalance.py
Go to the documentation of this file.
1"""
2High-level EnergyBalance interface for PyHelios.
3
4This module provides a user-friendly interface to the energy balance modeling
5capabilities with graceful plugin handling and informative error messages.
6"""
7
8import logging
9from typing import List, Optional, Union
10from contextlib import contextmanager
11
12from .plugins.registry import get_plugin_registry
13from .wrappers import UEnergyBalanceWrapper as energy_wrapper
14from .Context import Context, check_context_alive
15from .exceptions import HeliosError
16from .validation.plugin_decorators import (
17 validate_energy_run_params, validate_energy_band_params, validate_air_energy_params,
18 validate_evaluate_air_energy_params, validate_output_data_params, validate_print_report_params
19)
20
21logger = logging.getLogger(__name__)
22
23
25 """Exception raised for EnergyBalance-specific errors."""
26 pass
27
28
30 """
31 High-level interface for energy balance modeling and thermal calculations.
32
33 This class provides a user-friendly wrapper around the native Helios
34 energy balance plugin with automatic plugin availability checking and
35 graceful error handling.
36
37 The energy balance model computes surface temperatures based on local energy
38 balance equations, including radiation absorption, convection, and transpiration.
39 It supports both steady-state and dynamic (time-stepping) calculations.
40
41 System requirements:
42 - NVIDIA GPU with CUDA support
43 - CUDA Toolkit installed
44 - Energy balance plugin compiled into PyHelios
45
46 Example:
47 >>> with Context() as context:
48 ... # Add some geometry
49 ... patch_uuid = context.addPatch(center=[0, 0, 1], size=[1, 1])
50 ...
51 ... with EnergyBalanceModel(context) as energy_balance:
52 ... # Add radiation band for flux calculations
53 ... energy_balance.addRadiationBand("SW")
54 ...
55 ... # Run steady-state energy balance
56 ... energy_balance.run()
57 ...
58 ... # Or run dynamic simulation with timestep
59 ... energy_balance.run(dt=60.0) # 60 second timestep
60 """
61
62 def __init__(self, context: Context):
63 """
64 Initialize EnergyBalanceModel with graceful plugin handling.
65
66 Args:
67 context: Helios Context instance
68
69 Raises:
70 TypeError: If context is not a Context instance
71 EnergyBalanceModelError: If energy balance plugin is not available
72 """
73 # Validate context type
74 if not isinstance(context, Context):
75 raise TypeError(f"EnergyBalanceModel requires a Context instance, got {type(context).__name__}")
76
77 self.context = context
78 self.energy_model = None
79
80 # Check plugin availability using registry
81 registry = get_plugin_registry()
82
83 if not registry.is_plugin_available('energybalance'):
84 # Get helpful information about the missing plugin
85 plugin_info = registry.get_plugin_capabilities()
86 available_plugins = registry.get_available_plugins()
87
88 error_msg = (
89 "EnergyBalanceModel requires the 'energybalance' plugin which is not available.\n\n"
90 "The energy balance plugin provides GPU-accelerated thermal modeling and surface temperature calculations.\n"
91 "System requirements:\n"
92 "- NVIDIA GPU with CUDA support\n"
93 "- CUDA Toolkit installed\n"
94 "- Energy balance plugin compiled into PyHelios\n\n"
95 "To enable energy balance modeling:\n"
96 "1. Build PyHelios with energy balance plugin:\n"
97 " build_scripts/build_helios --plugins energybalance\n"
98 "2. Or build with multiple plugins:\n"
99 " build_scripts/build_helios --plugins energybalance,visualizer,weberpenntree\n"
100 f"\nCurrently available plugins: {available_plugins}"
101 )
102
103 # Suggest alternatives if available
104 alternatives = registry.suggest_alternatives('energybalance')
105 if alternatives:
106 error_msg += f"\n\nAlternative plugins available: {alternatives}"
107 error_msg += "\nConsider using radiation or photosynthesis for related thermal modeling."
108
109 raise EnergyBalanceModelError(error_msg)
110
111 # Plugin is available - create energy balance model
112 try:
113 self.energy_model = energy_wrapper.createEnergyBalanceModel(context.getNativePtr())
114 if self.energy_model is None:
116 "Failed to create EnergyBalanceModel instance. "
117 "This may indicate a problem with the native library or CUDA initialization."
118 )
119 logger.info("EnergyBalanceModel created successfully")
120
121 except Exception as e:
122 raise EnergyBalanceModelError(f"Failed to initialize EnergyBalanceModel: {e}")
123
124 def _check_context_alive(self):
125 """Raise if the owning Context has been destroyed (see Context.check_context_alive)."""
126 check_context_alive(self.context, "EnergyBalanceModel")
128 def __enter__(self):
129 """Context manager entry."""
130 return self
132 def __exit__(self, exc_type, exc_value, traceback):
133 """Context manager exit with proper cleanup."""
134 if self.energy_model is not None:
135 try:
136 energy_wrapper.destroyEnergyBalanceModel(self.energy_model)
137 logger.debug("EnergyBalanceModel destroyed successfully")
138 except Exception as e:
139 logger.warning(f"Error destroying EnergyBalanceModel: {e}")
140 finally:
141 self.energy_model = None
142
143 def __del__(self):
144 """Destructor to ensure C++ resources freed even without 'with' statement."""
145 if hasattr(self, 'energy_model') and self.energy_model is not None:
146 try:
147 energy_wrapper.destroyEnergyBalanceModel(self.energy_model)
148 self.energy_model = None
149 except Exception as e:
150 import warnings
151 warnings.warn(f"Error in EnergyBalanceModel.__del__: {e}")
152
153 def getNativePtr(self):
154 """Get the native pointer for advanced operations."""
155 return self.energy_model
157 def enableMessages(self) -> None:
158 """
159 Enable console output messages from the energy balance model.
160
161 Raises:
162 EnergyBalanceModelError: If operation fails
163 """
165 try:
166 energy_wrapper.enableMessages(self.energy_model)
167 except Exception as e:
168 raise EnergyBalanceModelError(f"Failed to enable messages: {e}")
169
170 def disableMessages(self) -> None:
171 """
172 Disable console output messages from the energy balance model.
173
174 Raises:
175 EnergyBalanceModelError: If operation fails
176 """
178 try:
179 energy_wrapper.disableMessages(self.energy_model)
180 except Exception as e:
181 raise EnergyBalanceModelError(f"Failed to disable messages: {e}")
182
183 @validate_energy_run_params
184 def run(self, uuids: Optional[List[int]] = None, dt: Optional[float] = None) -> None:
185 """
186 Run the energy balance model.
187
188 This method supports multiple execution modes:
189 - Steady state for all primitives: run()
190 - Dynamic with timestep for all primitives: run(dt=60.0)
191 - Steady state for specific primitives: run(uuids=[1, 2, 3])
192 - Dynamic with timestep for specific primitives: run(uuids=[1, 2, 3], dt=60.0)
193
194 Args:
195 uuids: Optional list of primitive UUIDs to process. If None, processes all primitives.
196 dt: Optional timestep in seconds for dynamic simulation. If None, runs steady-state.
197
198 Raises:
199 ValueError: If parameters are invalid
200 EnergyBalanceModelError: If energy balance calculation fails
201
202 Example:
203 >>> # Steady state for all primitives
204 >>> energy_balance.run()
205
206 >>> # Dynamic simulation with 60-second timestep
207 >>> energy_balance.run(dt=60.0)
208
209 >>> # Steady state for specific patches
210 >>> energy_balance.run(uuids=[patch1_uuid, patch2_uuid])
211
212 >>> # Dynamic simulation for specific patches
213 >>> energy_balance.run(uuids=[patch1_uuid, patch2_uuid], dt=30.0)
214 """
216 try:
217 if uuids is None and dt is None:
218 # Steady state for all primitives
219 energy_wrapper.run(self.energy_model)
220 elif uuids is None and dt is not None:
221 # Dynamic with timestep for all primitives
222 energy_wrapper.runDynamic(self.energy_model, dt)
223 elif uuids is not None and dt is None:
224 # Steady state for specific primitives
225 energy_wrapper.runForUUIDs(self.energy_model, uuids)
226 else:
227 # Dynamic with timestep for specific primitives
228 energy_wrapper.runForUUIDsDynamic(self.energy_model, uuids, dt)
229
230 except Exception as e:
231 raise EnergyBalanceModelError(f"Energy balance calculation failed: {e}")
232
233 @validate_energy_band_params
234 def addRadiationBand(self, band: Union[str, List[str]]) -> None:
235 """
236 Add a radiation band or bands for absorbed flux calculations.
237
238 The energy balance model uses radiation bands from the RadiationModel
239 plugin to calculate absorbed radiation flux for each primitive.
240
241 Args:
242 band: Name of radiation band (e.g., "SW", "PAR", "NIR", "LW")
243 or list of band names
244
245 Raises:
246 ValueError: If band name is invalid
247 EnergyBalanceModelError: If operation fails
248
249 Example:
250 >>> energy_balance.addRadiationBand("SW") # Single band
251 >>> energy_balance.addRadiationBand(["SW", "LW", "PAR"]) # Multiple bands
252 """
253 if isinstance(band, str):
254 if not band:
255 raise ValueError("Band name must be a non-empty string")
257 try:
258 energy_wrapper.addRadiationBand(self.energy_model, band)
259 except Exception as e:
260 raise EnergyBalanceModelError(f"Failed to add radiation band '{band}': {e}")
261 elif isinstance(band, list):
262 if not band:
263 raise ValueError("Bands list cannot be empty")
264 for b in band:
265 if not isinstance(b, str) or not b:
266 raise ValueError("All band names must be non-empty strings")
268 try:
269 energy_wrapper.addRadiationBands(self.energy_model, band)
270 except Exception as e:
271 raise EnergyBalanceModelError(f"Failed to add radiation bands {band}: {e}")
272 else:
273 raise ValueError("Band must be a string or list of strings")
274
275 @validate_air_energy_params
276 def enableAirEnergyBalance(self, canopy_height_m: Optional[float] = None,
277 reference_height_m: Optional[float] = None) -> None:
278 """
279 Enable air energy balance model for canopy-scale thermal calculations.
280
281 The air energy balance computes average air temperature and water vapor
282 mole fraction based on the energy balance of the air layer in the canopy.
283
284 Args:
285 canopy_height_m: Optional canopy height in meters. If not provided,
286 computed automatically from primitive bounding box.
287 reference_height_m: Optional reference height in meters where ambient
288 conditions are measured. If not provided, assumes
289 reference height is at canopy top.
290
291 Raises:
292 ValueError: If parameters are invalid
293 EnergyBalanceModelError: If operation fails
294
295 Example:
296 >>> # Automatic canopy height detection
297 >>> energy_balance.enable_air_energy_balance()
298
299 >>> # Manual canopy and reference heights
300 >>> energy_balance.enable_air_energy_balance(canopy_height_m=5.0, reference_height_m=10.0)
301 """
302 if canopy_height_m is not None and canopy_height_m <= 0:
303 raise ValueError("Canopy height must be positive")
304 if reference_height_m is not None and reference_height_m <= 0:
305 raise ValueError("Reference height must be positive")
306
308 try:
309 if canopy_height_m is None and reference_height_m is None:
310 energy_wrapper.enableAirEnergyBalance(self.energy_model)
311 elif canopy_height_m is not None and reference_height_m is not None:
312 energy_wrapper.enableAirEnergyBalanceWithParameters(
313 self.energy_model, canopy_height_m, reference_height_m)
314 else:
315 raise ValueError("Both canopy_height_m and reference_height_m must be provided together, or both None")
316
317 except Exception as e:
318 raise EnergyBalanceModelError(f"Failed to enable air energy balance: {e}")
319
320 def enableCanopyAirspaceModel(self, canopy_UUIDs: List[int], canopy_height_m: float,
321 reference_height_m: float, leaf_area_index: float,
322 num_layers: int = 1,
323 ground_UUIDs: Optional[List[int]] = None) -> None:
324 """
325 Enable the canopy airspace model.
326
327 Resolves the within-canopy air temperature and humidity surrounding leaves from a
328 vertically layered resistance network, instead of holding them at a prescribed
329 value, so the canopy feeds back on the air driving its own transpiration. The
330 airspace is divided into vertical layers of equal leaf area index, each exchanging
331 sensible heat and water vapor with its leaves, its neighboring layers and -- for
332 the outermost layers -- the soil surface and the above-canopy reference air.
333
334 Unlike :meth:`enableAirEnergyBalance`, which evolves a prognostic boundary layer
335 and thereby assumes a horizontally infinite canopy, this model suits canopies of
336 limited extent subject to advection, such as an orchard block. The two determine
337 the same air state and **may not both be enabled**.
338
339 Because it solves for a steady state, it cannot be combined with the form of
340 :meth:`run` that takes a timestep.
341
342 The above-canopy boundary condition is read from global data
343 ``air_temperature_reference``, ``air_humidity_reference`` and
344 ``wind_speed_reference`` when present, otherwise from this model's defaults. Set
345 them with :meth:`Context.setGlobalData` before calling :meth:`run`.
346
347 After :meth:`run`, primitive data ``air_temperature``, ``air_humidity`` and
348 ``wind_speed`` are set on the canopy primitives, and global data
349 ``canopy_air_temperature``, ``canopy_air_humidity``, their per-layer counterparts,
350 ``aerodynamic_resistance`` and ``canopy_airspace_iterations`` are reported.
351
352 Args:
353 canopy_UUIDs: Canopy (leaf) primitives exchanging heat and moisture with the airspace.
354 canopy_height_m: Height of the canopy in meters.
355 reference_height_m: Height at which above-canopy conditions are measured, in
356 meters. Must be greater than ``canopy_height_m``.
357 leaf_area_index: One-sided leaf area index on a ground-area basis.
358 num_layers: Number of vertical layers of equal leaf area index. 1 gives a
359 single within-canopy node.
360 ground_UUIDs: Ground primitives forming the soil node beneath the canopy. When
361 omitted there is no exchange with the soil surface.
362
363 Raises:
364 ValueError: If parameters are invalid.
365 EnergyBalanceModelError: If the operation fails.
366
367 Example:
368 >>> energy_balance.enableCanopyAirspaceModel(
369 ... canopy_UUIDs=leaf_uuids, canopy_height_m=3.0, reference_height_m=5.0,
370 ... leaf_area_index=2.5, num_layers=5, ground_UUIDs=ground_uuids)
371 >>> energy_balance.run()
372 """
373 if not canopy_UUIDs:
374 raise ValueError("canopy_UUIDs must contain at least one UUID")
375 if canopy_height_m <= 0:
376 raise ValueError(f"Canopy height must be positive, got {canopy_height_m}")
377 if reference_height_m <= canopy_height_m:
378 raise ValueError(
379 f"Reference height ({reference_height_m}) must be greater than "
380 f"canopy height ({canopy_height_m})")
381 if leaf_area_index <= 0:
382 raise ValueError(f"Leaf area index must be positive, got {leaf_area_index}")
383 if num_layers < 1:
384 raise ValueError(f"Number of layers must be at least 1, got {num_layers}")
385
387 try:
388 energy_wrapper.enableCanopyAirspaceModel(
389 self.energy_model, canopy_UUIDs, ground_UUIDs or [],
390 canopy_height_m, reference_height_m, leaf_area_index, num_layers)
391 except Exception as e:
392 raise EnergyBalanceModelError(f"Failed to enable canopy airspace model: {e}")
393
394 def disableCanopyAirspaceModel(self) -> None:
395 """
396 Disable the canopy airspace model.
397
398 Subsequent calls to :meth:`run` perform a single surface energy balance pass using
399 whatever ``air_temperature`` and ``air_humidity`` primitive data are currently set.
400
401 Raises:
402 EnergyBalanceModelError: If the operation fails.
403 """
405 try:
406 energy_wrapper.disableCanopyAirspaceModel(self.energy_model)
407 except Exception as e:
408 raise EnergyBalanceModelError(f"Failed to disable canopy airspace model: {e}")
409
410 def setCanopyAirspaceConvergence(self, tolerance_K: float = 0.01,
411 max_iterations: int = 50) -> None:
412 """
413 Set convergence criteria for the canopy airspace iteration.
414
415 Args:
416 tolerance_K: Temperature convergence tolerance in Kelvin. Iteration stops when
417 the maximum change in any layer's air temperature falls below this value.
418 max_iterations: Maximum number of coupled surface-energy-balance and airspace
419 iterations.
420
421 Raises:
422 ValueError: If parameters are invalid.
423 EnergyBalanceModelError: If the operation fails.
424 """
425 if tolerance_K <= 0:
426 raise ValueError(f"Convergence tolerance must be positive, got {tolerance_K}")
427 if max_iterations < 1:
428 raise ValueError(f"Maximum iterations must be at least 1, got {max_iterations}")
429
431 try:
432 energy_wrapper.setCanopyAirspaceConvergence(
433 self.energy_model, tolerance_K, max_iterations)
434 except Exception as e:
435 raise EnergyBalanceModelError(f"Failed to set canopy airspace convergence: {e}")
436
437 @validate_evaluate_air_energy_params
438 def evaluateAirEnergyBalance(self, dt_sec: float, time_advance_sec: float,
439 UUIDs: Optional[List[int]] = None) -> None:
440 """
441 Advance the air energy balance over time.
442
443 This method advances the air energy balance model by integrating over
444 multiple timesteps to reach the target time advancement.
445
446 Args:
447 dt_sec: Timestep in seconds for integration
448 time_advance_sec: Total time to advance in seconds (must be >= dt_sec)
449 UUIDs: Optional list of primitive UUIDs. If None, processes all primitives.
450
451 Raises:
452 ValueError: If parameters are invalid
453 EnergyBalanceModelError: If operation fails
454
455 Example:
456 >>> # Advance air energy balance by 1 hour using 60-second timesteps
457 >>> energy_balance.evaluate_air_energy_balance(dt_sec=60.0, time_advance_sec=3600.0)
458
459 >>> # Advance for specific primitives
460 >>> energy_balance.evaluate_air_energy_balance(
461 ... dt_sec=30.0, time_advance_sec=1800.0, uuids=[patch1_uuid, patch2_uuid])
462 """
463 if dt_sec <= 0:
464 raise ValueError("Time step must be positive")
465 if time_advance_sec < dt_sec:
466 raise ValueError("Total time advance must be greater than or equal to time step")
467
468 try:
469 if UUIDs is None:
470 energy_wrapper.evaluateAirEnergyBalance(self.energy_model, dt_sec, time_advance_sec)
471 else:
472 energy_wrapper.evaluateAirEnergyBalanceForUUIDs(
473 self.energy_model, UUIDs, dt_sec, time_advance_sec)
474
475 except Exception as e:
476 raise EnergyBalanceModelError(f"Failed to evaluate air energy balance: {e}")
477
478 @validate_output_data_params
479 def optionalOutputPrimitiveData(self, label: str) -> None:
480 """
481 Add optional output primitive data to the Context.
482
483 This method adds additional data fields to primitives that will be
484 calculated and stored during energy balance execution.
485
486 Args:
487 label: Name of the data field to add (e.g., "vapor_pressure_deficit",
488 "boundary_layer_conductance", "net_radiation")
489
490 Raises:
491 ValueError: If label is invalid
492 EnergyBalanceModelError: If operation fails
493
494 Example:
495 >>> energy_balance.add_optional_output_data("vapor_pressure_deficit")
496 >>> energy_balance.add_optional_output_data("net_radiation")
497 """
498 if not label or not isinstance(label, str):
499 raise ValueError("Label must be a non-empty string")
500
502 try:
503 energy_wrapper.optionalOutputPrimitiveData(self.energy_model, label)
504 except Exception as e:
505 raise EnergyBalanceModelError(f"Failed to add optional output data '{label}': {e}")
506
507 @validate_print_report_params
508 def printDefaultValueReport(self, UUIDs: Optional[List[int]] = None) -> None:
509 """
510 Print a report detailing usage of default input values.
511
512 This diagnostic method prints information about which primitives are
513 using default values for energy balance parameters, helping identify
514 where additional parameter specification might be needed.
515
516 Args:
517 UUIDs: Optional list of primitive UUIDs to report on. If None,
518 reports on all primitives.
519
520 Raises:
521 EnergyBalanceModelError: If operation fails
522
523 Example:
524 >>> # Report on all primitives
525 >>> energy_balance.print_default_value_report()
526
527 >>> # Report on specific primitives
528 >>> energy_balance.print_default_value_report(uuids=[patch1_uuid, patch2_uuid])
529 """
530 try:
531 if UUIDs is None:
532 energy_wrapper.printDefaultValueReport(self.energy_model)
533 else:
534 energy_wrapper.printDefaultValueReportForUUIDs(self.energy_model, UUIDs)
535
536 except Exception as e:
537 raise EnergyBalanceModelError(f"Failed to print default value report: {e}")
538
539 def is_available(self) -> bool:
540 """
541 Check if EnergyBalanceModel is available in current build.
542
543 Returns:
544 True if plugin is available, False otherwise
545 """
546 registry = get_plugin_registry()
547 return registry.is_plugin_available('energybalance')
548
549 def enableGPUAcceleration(self) -> None:
550 """
551 Enable GPU acceleration for energy balance calculations.
552
553 Attempts to enable GPU acceleration using CUDA. If GPU is not available at runtime,
554 this will raise an error. The energy balance model will use three-tier execution:
555 GPU (CUDA), OpenMP (parallel CPU), or serial CPU fallback.
556
557 Raises:
558 NotImplementedError: If library not compiled with CUDA support
559 EnergyBalanceModelError: If GPU acceleration cannot be enabled
560
561 Example:
562 >>> with EnergyBalanceModel(context) as energy_balance:
563 ... try:
564 ... energy_balance.enableGPUAcceleration()
565 ... print("GPU acceleration enabled")
566 ... except NotImplementedError:
567 ... print("GPU not available - using CPU mode")
568
569 Note:
570 Only available when PyHelios is compiled with CUDA support.
571 OpenMP CPU mode is recommended for most workloads without GPU.
572 """
574 try:
575 energy_wrapper.enableGPUAcceleration(self.energy_model)
576 except NotImplementedError:
577 raise
578 except Exception as e:
579 raise EnergyBalanceModelError(f"Failed to enable GPU acceleration: {e}")
580
581 def disableGPUAcceleration(self) -> None:
582 """
583 Disable GPU acceleration and force CPU mode.
584
585 Forces the use of OpenMP CPU implementation even if GPU is available.
586 Useful for testing, benchmarking, or when CPU performance is preferred.
587
588 Raises:
589 EnergyBalanceModelError: If operation fails
590
591 Example:
592 >>> energy_balance.disableGPUAcceleration()
593
594 Note:
595 Only available when PyHelios is compiled with CUDA support.
596 Has no effect if GPU support is not compiled in.
597 """
599 try:
600 energy_wrapper.disableGPUAcceleration(self.energy_model)
601 except Exception as e:
602 raise EnergyBalanceModelError(f"Failed to disable GPU acceleration: {e}")
603
604 def isGPUAccelerationEnabled(self) -> bool:
605 """
606 Check if GPU acceleration is currently enabled.
607
608 Returns:
609 True if GPU acceleration is enabled and available, False otherwise
610
611 Example:
612 >>> if energy_balance.isGPUAccelerationEnabled():
613 ... print("Using GPU acceleration")
614 ... else:
615 ... print("Using CPU mode")
616
617 Note:
618 Returns False if library not compiled with CUDA support.
619 """
620 try:
621 return energy_wrapper.isGPUAccelerationEnabled(self.energy_model)
622 except NotImplementedError:
623 return False
624 except Exception as e:
625 raise EnergyBalanceModelError(f"Failed to check GPU acceleration status: {e}")
626
627 @staticmethod
628 def isGPUAccelerationAvailable() -> bool:
629 """
630 Check if GPU acceleration functions are available in this build.
631
632 Returns:
633 True if GPU acceleration support is compiled in, False otherwise
634
635 Example:
636 >>> if EnergyBalanceModel.isGPUAccelerationAvailable():
637 ... print("GPU acceleration supported")
638 ... else:
639 ... print("GPU acceleration not compiled in - CPU mode only")
640 """
641 return energy_wrapper.isGPUAccelerationAvailable()
643
644# Convenience function
645def create_energy_balance_model(context: Context) -> EnergyBalanceModel:
646 """
647 Create EnergyBalanceModel instance with context.
648
649 Args:
650 context: Helios Context
651
652 Returns:
653 EnergyBalanceModel instance
654 """
655 return EnergyBalanceModel(context)
Exception raised for EnergyBalance-specific errors.
High-level interface for energy balance modeling and thermal calculations.
__init__(self, Context context)
Initialize EnergyBalanceModel with graceful plugin handling.
None optionalOutputPrimitiveData(self, str label)
Add optional output primitive data to the Context.
__del__(self)
Destructor to ensure C++ resources freed even without 'with' statement.
bool is_available(self)
Check if EnergyBalanceModel is available in current build.
None disableMessages(self)
Disable console output messages from the energy balance model.
None enableMessages(self)
Enable console output messages from the energy balance model.
bool isGPUAccelerationEnabled(self)
Check if GPU acceleration is currently enabled.
bool isGPUAccelerationAvailable()
Check if GPU acceleration functions are available in this build.
None run(self, Optional[List[int]] uuids=None, Optional[float] dt=None)
Run the energy balance model.
None printDefaultValueReport(self, Optional[List[int]] UUIDs=None)
Print a report detailing usage of default input values.
None setCanopyAirspaceConvergence(self, float tolerance_K=0.01, int max_iterations=50)
Set convergence criteria for the canopy airspace iteration.
__enter__(self)
Context manager entry.
None addRadiationBand(self, Union[str, List[str]] band)
Add a radiation band or bands for absorbed flux calculations.
None enableCanopyAirspaceModel(self, List[int] canopy_UUIDs, float canopy_height_m, float reference_height_m, float leaf_area_index, int num_layers=1, Optional[List[int]] ground_UUIDs=None)
Enable the canopy airspace model.
__exit__(self, exc_type, exc_value, traceback)
Context manager exit with proper cleanup.
_check_context_alive(self)
Raise if the owning Context has been destroyed (see Context.check_context_alive).
getNativePtr(self)
Get the native pointer for advanced operations.
None enableGPUAcceleration(self)
Enable GPU acceleration for energy balance calculations.
None disableGPUAcceleration(self)
Disable GPU acceleration and force CPU mode.
None disableCanopyAirspaceModel(self)
Disable the canopy airspace model.
None evaluateAirEnergyBalance(self, float dt_sec, float time_advance_sec, Optional[List[int]] UUIDs=None)
Advance the air energy balance over time.
None enableAirEnergyBalance(self, Optional[float] canopy_height_m=None, Optional[float] reference_height_m=None)
Enable air energy balance model for canopy-scale thermal calculations.
Exception classes for PyHelios library.
Definition exceptions.py:10
EnergyBalanceModel create_energy_balance_model(Context context)
Create EnergyBalanceModel instance with context.