Overview
The Energy Balance plugin provides GPU-accelerated thermal modeling and surface temperature calculations for plant canopies and individual primitives. It computes surface temperatures based on local energy balance equations, including radiation absorption, convection, and transpiration processes.
The plugin supports both steady-state and dynamic (time-stepping) calculations, making it suitable for a wide range of thermal modeling applications from instantaneous temperature estimation to long-term thermal dynamics simulation.
System Requirements
- Platforms: Windows, Linux, macOS
- GPU: NVIDIA GPU with CUDA support (required)
- Dependencies: CUDA Toolkit installed
- Memory: Scales with geometry complexity and simulation duration
Installation
The Energy Balance plugin is included in the following build profiles:
gpu-accelerated
: High-performance GPU features
research
: Comprehensive research suite
physics
: Comprehensive physics modeling
Build with Energy Balance
# Using profile
build_scripts/build_helios --interactive
# Explicit selection
build_scripts/build_helios --plugins energybalance
# Check if available
python -c "from pyhelios.plugins import print_plugin_status; print_plugin_status()"
Verify Installation
from pyhelios import EnergyBalanceModel
from pyhelios.plugins.registry import get_plugin_registry
registry = get_plugin_registry()
if registry.is_plugin_available('energybalance'):
print("Energy Balance plugin is available")
else:
print("Energy Balance plugin is not available")
Quick Start
from pyhelios import Context, EnergyBalanceModel
with Context() as context:
patch_uuid = context.addPatch(center=vec3(0, 0, 1), size=vec2(1, 1))
with EnergyBalanceModel(context) as energy_balance:
energy_balance.addRadiationBand("SW")
energy_balance.run()
print("Steady-state energy balance complete")
energy_balance.run(dt=60.0)
print("Dynamic energy balance complete")
API Reference
EnergyBalanceModel Class
Constructor
EnergyBalanceModel(context: Context)
Initialize EnergyBalanceModel with a Helios context.
Parameters:
context
: Active Helios Context instance
Raises:
TypeError
: If context is not a Context instance
EnergyBalanceModelError
: If energy balance plugin not available
RuntimeError
: If initialization fails
Core Methods
run()
run(uuids: Optional[List[int]] = None, dt: Optional[float] = None) -> None
Run the energy balance model with flexible execution modes.
Parameters:
uuids
: Optional list of primitive UUIDs to process. If None, processes all primitives.
dt
: Optional timestep in seconds for dynamic simulation. If None, runs steady-state.
Execution Modes:
run()
- Steady state for all primitives
run(dt=60.0)
- Dynamic with timestep for all primitives
run(uuids=[1, 2, 3])
- Steady state for specific primitives
run(uuids=[1, 2, 3], dt=60.0)
- Dynamic with timestep for specific primitives
Raises:
ValueError
: If parameters are invalid
EnergyBalanceModelError
: If energy balance calculation fails
Example:
energy_balance.run()
energy_balance.run(dt=60.0)
energy_balance.run(uuids=[patch1_uuid, patch2_uuid])
energy_balance.run(uuids=[patch1_uuid, patch2_uuid], dt=30.0)
addRadiationBand()
addRadiationBand(band: Union[str, List[str]]) -> None
Add a radiation band or bands for absorbed flux calculations.
Parameters:
band
: Name of radiation band (e.g., "SW", "PAR", "NIR", "LW") or list of band names
Raises:
ValueError
: If band name is invalid
EnergyBalanceModelError
: If operation fails
Example:
energy_balance.addRadiationBand("SW")
energy_balance.addRadiationBand(["SW", "LW"])
enableAirEnergyBalance()
enableAirEnergyBalance(canopy_height_m: Optional[float] = None,
reference_height_m: Optional[float] = None) -> None
Enable air energy balance model for canopy-scale thermal calculations.
Parameters:
canopy_height_m
: Optional canopy height in meters. If not provided, computed automatically.
reference_height_m
: Optional reference height in meters where ambient conditions are measured.
Raises:
ValueError
: If parameters are invalid
EnergyBalanceModelError
: If operation fails
Example:
energy_balance.enableAirEnergyBalance()
energy_balance.enableAirEnergyBalance(canopy_height_m=5.0, reference_height_m=10.0)
evaluateAirEnergyBalance()
evaluateAirEnergyBalance(dt_sec: float, time_advance_sec: float,
uuids: Optional[List[int]] = None) -> None
Advance the air energy balance over time.
Parameters:
dt_sec
: Timestep in seconds for integration
time_advance_sec
: Total time to advance in seconds (must be >= dt_sec)
uuids
: Optional list of primitive UUIDs. If None, processes all primitives.
Raises:
ValueError
: If parameters are invalid
EnergyBalanceModelError
: If operation fails
Example:
energy_balance.evaluateAirEnergyBalance(dt_sec=60.0, time_advance_sec=3600.0)
energy_balance.evaluateAirEnergyBalance(
dt_sec=30.0, time_advance_sec=1800.0, uuids=[patch1_uuid, patch2_uuid])
Utility Methods
optionalOutputPrimitiveData()
optionalOutputPrimitiveData(label: str) -> None
Add optional output primitive data to the Context.
Parameters:
label
: Name of data field to add (e.g., "vapor_pressure_deficit")
Example:
energy_balance.optionalOutputPrimitiveData("vapor_pressure_deficit")
energy_balance.optionalOutputPrimitiveData("net_radiation")
printDefaultValueReport()
printDefaultValueReport(uuids: Optional[List[int]] = None) -> None
Print diagnostic report of default input value usage.
Parameters:
uuids
: Optional list of primitive UUIDs to report on. If None, reports on all primitives.
enableMessages() / disableMessages()
enableMessages() -> None
disableMessages() -> None
Control console output from the energy balance model.
Examples
Basic Energy Balance Calculation
from pyhelios import Context, EnergyBalanceModel
with Context() as context:
patch_uuid = context.addPatch(center=vec3(0, 0, 1), size=vec2(1, 1))
with EnergyBalanceModel(context) as energy_balance:
energy_balance.addRadiationBand("SW")
energy_balance.run()
print("Steady-state energy balance complete")
temperature = context.getPrimitiveData(patch_uuid, "temperature")
print(f"Surface temperature: {temperature[0]:.2f} K")
Dynamic Thermal Simulation
from pyhelios import Context, EnergyBalanceModel
import numpy as np
with Context() as context:
patch_uuids = []
for i in range(10):
for j in range(10):
uuid = context.addPatch(
center=vec3(i*0.5, j*0.5, 2.0),
size=vec2(0.4, 0.4)
)
patch_uuids.append(uuid)
with EnergyBalanceModel(context) as energy_balance:
energy_balance.addRadiationBand(["SW", "LW"])
timesteps = np.arange(0, 3600, 60)
temperatures = []
for t in timesteps:
energy_balance.run(dt=60.0)
temp_data = []
for uuid in patch_uuids[:5]:
temp = context.getPrimitiveData(uuid, "temperature")
temp_data.append(temp[0])
temperatures.append(np.mean(temp_data))
print(f"t={t:4.0f}s: Mean temperature = {temperatures[-1]:.2f} K")
Air Energy Balance Modeling
from pyhelios import Context, EnergyBalanceModel
with Context() as context:
for layer in range(3):
z_height = (layer + 1) * 1.5
for i in range(8):
for j in range(8):
context.addPatch(
center=vec3(i*0.6, j*0.6, z_height),
size=vec2(0.5, 0.5)
)
with EnergyBalanceModel(context) as energy_balance:
energy_balance.addRadiationBand("SW")
energy_balance.enableAirEnergyBalance(
canopy_height_m=4.5,
reference_height_m=10.0
)
energy_balance.optionalOutputPrimitiveData("air_temperature")
energy_balance.optionalOutputPrimitiveData("air_humidity")
energy_balance.evaluateAirEnergyBalance(
dt_sec=60.0,
time_advance_sec=7200.0
)
print("Air energy balance simulation complete")
Integration with Radiation Modeling
from pyhelios import Context, RadiationModel, EnergyBalanceModel
with Context() as context:
for i in range(5):
for j in range(5):
context.addPatch(center=vec3(i, j, 2.0), size=vec2(0.8, 0.8))
with RadiationModel(context) as radiation:
radiation.addRadiationBand("SW")
source_id = radiation.addCollimatedRadiationSource()
radiation.setSourceFlux(source_id, "SW", 1000.0)
radiation.runBand("SW")
with EnergyBalanceModel(context) as energy_balance:
energy_balance.addRadiationBand("SW")
energy_balance.enableAirEnergyBalance()
energy_balance.run(dt=300.0)
print("Coupled radiation-thermal simulation complete")
Error Handling
from pyhelios import Context, EnergyBalanceModel, EnergyBalanceModelError
with Context() as context:
try:
with EnergyBalanceModel(context) as energy_balance:
energy_balance.addRadiationBand("SW")
energy_balance.run()
except EnergyBalanceModelError as e:
print(f"Energy balance error: {e}")
except ValueError as e:
print(f"Parameter error: {e}")
Parameter Validation Examples
with Context() as context:
with EnergyBalanceModel(context) as energy_balance:
try:
energy_balance.run(dt=-1.0)
except ValueError as e:
print(f"Invalid timestep: {e}")
try:
energy_balance.enableAirEnergyBalance(canopy_height_m=-5.0)
except ValueError as e:
print(f"Invalid canopy height: {e}")
try:
energy_balance.addRadiationBand("")
except ValueError as e:
print(f"Invalid band name: {e}")
Troubleshooting
Plugin Not Available
If you see "EnergyBalanceModel not available" errors:
- Check plugin status:
python -c "from pyhelios.plugins import print_plugin_status; print_plugin_status()"
- Rebuild with energy balance plugin:
build_scripts/build_helios --clean --plugins energybalance
- Verify CUDA installation:
nvidia-smi # Check GPU status
nvcc --version # Check CUDA compiler
Build Errors
Common build issues:
- Missing CUDA: Install CUDA Toolkit from NVIDIA
- GPU compatibility: Energy balance requires CUDA-capable GPU
- Memory issues: Ensure sufficient GPU memory for geometry size
- CMake errors: Verify CMake version and CUDA paths
Runtime Errors
- Parameter validation errors: Check parameter types, ranges, and combinations
- CUDA errors: Verify GPU memory availability and CUDA driver compatibility
- Context errors: Ensure Context contains geometry before running energy balance
- Memory errors: Use context managers for proper cleanup
Performance Issues
- Large geometry: Energy balance computation scales with primitive count
- Dynamic simulations: Small timesteps increase computation time
- Air energy balance: Additional computational overhead for canopy-scale modeling
- Memory usage: GPU memory scales with geometry complexity
Performance Notes
- Energy balance computations are GPU-accelerated using CUDA
- Performance scales approximately linearly with primitive count
- Dynamic simulations require more computation than steady-state
- Air energy balance adds computational overhead but provides canopy-scale modeling
- Memory usage is primarily on GPU, scaling with geometry size and simulation duration
Limitations
- Requires NVIDIA GPU with CUDA support
- GPU memory limits maximum geometry complexity
- Dynamic timesteps must be positive and reasonable for physical simulation
- Air energy balance requires canopy geometry for meaningful results
- Integration with RadiationModel provides best results for absorbed flux calculations
Advanced Usage
Custom Output Data Fields
The energy balance model can output various diagnostic quantities:
output_fields = [
"vapor_pressure_deficit",
"net_radiation",
"sensible_heat_flux",
"latent_heat_flux",
"boundary_layer_conductance",
"air_temperature",
"air_humidity"
]
for field in output_fields:
energy_balance.optionalOutputPrimitiveData(field)
Multi-Scale Modeling
energy_balance.enableAirEnergyBalance(canopy_height_m=6.0, reference_height_m=15.0)
energy_balance.run(dt=60.0)
energy_balance.evaluateAirEnergyBalance(dt_sec=60.0, time_advance_sec=3600.0)
Default Value Diagnostics
energy_balance.printDefaultValueReport()