Skip to content

EEMT API Reference

Overview

This reference provides detailed documentation for all EEMT calculation functions, GRASS GIS commands, and configuration parameters.

Core EEMT Functions

Traditional EEMT Calculation

def calculate_eemt_traditional(temperature, precipitation, elevation=None):
    """
    Calculate EEMT using traditional climate-based approach

    Parameters:
    -----------
    temperature : array_like
        Monthly mean temperature [°C]
    precipitation : array_like  
        Monthly precipitation [mm]
    elevation : array_like, optional
        Elevation for lapse rate corrections [m]

    Returns:
    --------
    eemt : array_like
        Effective Energy and Mass Transfer [MJ m⁻² yr⁻¹]
    e_bio : array_like
        Biological energy component [MJ m⁻² yr⁻¹]
    e_ppt : array_like
        Precipitation energy component [MJ m⁻² yr⁻¹]

    Notes:
    ------
    Based on Rasmussen et al. (2005, 2014) methodology.
    Uses Lieth (1975) NPP equation and Hamon PET estimation.

    Examples:
    ---------
    >>> temp = np.array([5, 10, 15, 20, 18, 12, 8])  # Monthly temps
    >>> precip = np.array([50, 60, 80, 40, 30, 45, 55])  # Monthly precip
    >>> eemt, e_bio, e_ppt = calculate_eemt_traditional(temp, precip)
    >>> print(f"Annual EEMT: {eemt:.1f} MJ/m²/yr")
    """

Topographic EEMT Calculation

def calculate_eemt_topographic(dem_file, climate_data, solar_data, 
                             output_dir, mcwi_method='d_infinity'):
    """
    Calculate EEMT with topographic controls on energy and water balance

    Parameters:
    -----------
    dem_file : str or Path
        Path to digital elevation model (GeoTIFF)
    climate_data : dict
        Dictionary containing climate arrays:
        - 'temperature': Monthly temperature [°C] 
        - 'precipitation': Monthly precipitation [mm]
        - 'humidity': Relative humidity [%]
        - 'wind_speed': Wind speed [m/s]
    solar_data : dict
        Solar radiation data from r.sun calculations:
        - 'global_radiation': Monthly solar [Wh/m²]
        - 'diffuse_radiation': Diffuse component [Wh/m²]
        - 'direct_radiation': Direct beam component [Wh/m²]
    output_dir : str or Path
        Output directory for intermediate files
    mcwi_method : str, default 'd_infinity'
        Flow routing method: 'd_infinity', 'mfd', 'sfd'

    Returns:
    --------
    eemt_result : dict
        Results dictionary containing:
        - 'eemt': Total EEMT [MJ m⁻² yr⁻¹]
        - 'e_bio': Biological component [MJ m⁻² yr⁻¹] 
        - 'e_ppt': Precipitation component [MJ m⁻² yr⁻¹]
        - 'mcwi': Mass Conservative Wetness Index
        - 'solar_ratio': Topographic solar modification factor

    Notes:
    ------
    Implements Rasmussen et al. (2014) EEMT_TOPO methodology.
    Requires GRASS GIS for terrain analysis and solar calculations.

    Examples:
    ---------
    >>> climate = load_daymet_data('study_area.shp', 2015, 2020)
    >>> solar = calculate_annual_solar('dem.tif', threads=8)
    >>> result = calculate_eemt_topographic('dem.tif', climate, solar, 'output/')
    >>> print(f"Mean topographic EEMT: {np.mean(result['eemt']):.1f} MJ/m²/yr")
    """

Vegetation EEMT Calculation

def calculate_eemt_vegetation(dem_file, climate_data, vegetation_data,
                           output_dir, lai_method='ndvi', resistance_model='kelliher'):
    """
    Calculate EEMT with full vegetation and topographic integration

    Parameters:
    -----------
    dem_file : str or Path
        Digital elevation model file path
    climate_data : dict
        Complete climate dataset with:
        - 'temperature': Temperature arrays [°C]
        - 'precipitation': Precipitation arrays [mm] 
        - 'humidity': Relative humidity [%]
        - 'wind_speed': Wind speed [m/s]
        - 'net_radiation': Net radiation [W/m²]
    vegetation_data : dict
        Vegetation structure data:
        - 'lai': Leaf Area Index [-] 
        - 'canopy_height': Canopy height [m]
        - 'ndvi': Normalized Difference Vegetation Index [-]
        - 'biomass': Aboveground biomass [Mg/ha] (optional)
    output_dir : str or Path
        Output directory
    lai_method : str, default 'ndvi'
        LAI calculation method: 'ndvi', 'modis', 'direct'
    resistance_model : str, default 'kelliher' 
        Surface resistance model: 'kelliher', 'jarvis', 'stewart'

    Returns:
    --------
    eemt_result : dict
        Complete EEMT results:
        - 'eemt': Total EEMT [MJ m⁻² yr⁻¹]
        - 'e_bio': Biological energy [MJ m⁻² yr⁻¹]
        - 'e_ppt': Precipitation energy [MJ m⁻² yr⁻¹] 
        - 'aet': Actual evapotranspiration [mm/yr]
        - 'npp': Net primary production [kg/m²/yr]
        - 'surface_resistance': Surface resistance [s/m]
        - 'lai_effective': Effective LAI used in calculations

    Notes:
    ------
    Implements Rasmussen et al. (2014) EEMT_TOPO-VEG methodology.
    Uses Penman-Monteith equation with vegetation-specific parameters.
    Accounts for canopy structure effects on energy and water balance.

    Examples:
    ---------
    >>> # Load vegetation data from satellite
    >>> vegetation = {
    ...     'ndvi': load_landsat_ndvi('study_area.shp', 2020),
    ...     'canopy_height': load_lidar_canopy('lidar_data.las')
    ... }
    >>> result = calculate_eemt_vegetation('dem.tif', climate, vegetation, 'output/')
    >>> print(f"Vegetation EEMT: {np.mean(result['eemt']):.1f} MJ/m²/yr")
    """

GRASS GIS Command Reference

Solar Radiation (r.sun family)

r.sun (Multi-processor version)

r.sun elevation=dem aspect=aspect slope=slope \\
         day=180 step=0.25 \\
         linke_value=3.0 albedo_value=0.2 \\
         threads=8 \\
         glob_rad=global_radiation \\
         insol_time=sunshine_hours \\
         [beam_rad=beam_radiation] \\
         [diff_rad=diffuse_radiation] \\
         [refl_rad=reflected_radiation]

Parameters: - elevation=name - Input elevation raster - aspect=name - Aspect in degrees (0-360°)
- slope=name - Slope in degrees (0-90°) - day=integer - Day of year (1-365) - step=float - Time step in hours (0.25-1.0) - linke_value=float - Linke atmospheric turbidity (1.0-8.0) - albedo_value=float - Ground albedo (0.0-1.0) - threads=integer - Number of OpenMP threads - glob_rad=name - Output global radiation [Wh/m²] - insol_time=name - Output sunshine duration [hours]

r.sun (Single-processor version)

r.sun elevation=dem \\
      [aspect=aspect] [slope=slope] \\
      [lat=latitude] [lon=longitude] \\
      [day=day_of_year] [time=decimal_time] \\
      [step=time_step] \\
      [glob_rad=output] [beam_rad=output] \\
      [diff_rad=output] [refl_rad=output] \\
      [insol_time=output]

Advanced Options: - horizon_basename=basename - Horizon angle rasters - horizon_step=angle - Horizon calculation step [degrees] - civil_time=hour - Local solar time - solar_constant=value - Solar constant [W/m²] - distance_step=value - Sampling distance [m]

Terrain Analysis

r.slope.aspect

r.slope.aspect elevation=dem \\
               slope=slope_output \\
               aspect=aspect_output \\
               [format=degrees|percent] \\
               [precision=FCELL|DCELL] \\
               [zscale=factor] \\
               [min_slope=degrees]

r.terraflow (Flow accumulation)

r.terraflow elevation=dem \\
            filled=filled_dem \\
            direction=flow_direction \\
            swatershed=watersheds \\
            accumulation=flow_accumulation \\
            tci=topographic_convergence_index

r.watershed (Alternative flow routing)

r.watershed elevation=dem \\
            accumulation=flow_accum \\
            drainage=flow_direction \\
            basin=watersheds \\
            stream=stream_network \\
            [threshold=threshold_value] \\
            [-s] [-4] [-a]

Data Import/Export

r.in.gdal (Import raster data)

r.in.gdal input=input_file.tif \\
          output=grass_raster \\
          [band=band_number] \\
          [memory=memory_mb] \\
          [target=target_crs] \\
          [-o] [-e] [-f]

r.out.gdal (Export raster data)

r.out.gdal input=grass_raster \\
           output=output_file.tif \\
           format=GTiff \\
           [type=data_type] \\
           [nodata=nodata_value] \\
           createopt="COMPRESS=LZW,TILED=YES"

Configuration Parameters

Solar Radiation Parameters

Parameter Range Default Description
day 1-365 - Day of year for calculation
step 0.1-2.0 0.25 Time step interval [hours]
linke_value 1.0-8.0 3.0 Atmospheric turbidity factor
albedo_value 0.0-1.0 0.2 Surface albedo coefficient
lat -90 to 90 auto Latitude [decimal degrees]
solar_constant 1300-1400 1367 Solar constant [W/m²]

Processing Parameters

Parameter Range Default Description
threads 1-64 auto OpenMP thread count
memory 256-8192 2048 Memory cache [MB]
precision FCELL/DCELL FCELL Output precision
compress LZW/DEFLATE LZW Output compression

Climate Thresholds

Parameter Value Units Description
T_ref 273.15 K Reference temperature (freezing)
h_BIO 22×10⁶ J/kg Specific biomass enthalpy
c_w 4.18×10³ J/kg/K Specific heat of water
EEMT_threshold 70 MJ/m²/yr Carbon/water dominance transition

Error Handling

Common Error Codes

Error Cause Solution
GRASS: Location not found Invalid GRASS database Check GISDBASE path
GDAL: Cannot open file Missing input data Verify file paths
r.sun: Memory allocation failed Insufficient RAM Reduce region size or tile processing
r.sun: Invalid day parameter Day outside 1-365 range Check day parameter
Projection mismatch CRS inconsistency Reproject data to common CRS

Error Recovery Strategies

def robust_eemt_calculation(dem_file, climate_dir, output_dir, max_retries=3):
    """
    EEMT calculation with error recovery

    Implements automatic retry, fallback methods, and error logging
    """

    import logging
    from time import sleep

    # Configure logging
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s - %(levelname)s - %(message)s',
        handlers=[
            logging.FileHandler(f'{output_dir}/eemt_calculation.log'),
            logging.StreamHandler()
        ]
    )

    for attempt in range(max_retries):
        try:
            # Attempt EEMT calculation
            result = calculate_eemt_complete(dem_file, climate_dir, output_dir)
            logging.info(f"EEMT calculation successful on attempt {attempt + 1}")
            return result

        except MemoryError as e:
            logging.warning(f"Memory error on attempt {attempt + 1}: {e}")
            if attempt < max_retries - 1:
                # Try with reduced resolution
                logging.info("Retrying with reduced spatial resolution...")
                dem_file = reduce_resolution(dem_file, factor=2)
            else:
                raise

        except FileNotFoundError as e:
            logging.error(f"Missing input file: {e}")
            raise

        except subprocess.CalledProcessError as e:
            logging.warning(f"GRASS command failed on attempt {attempt + 1}: {e}")
            if attempt < max_retries - 1:
                sleep(5)  # Wait before retry
                logging.info("Retrying GRASS operation...")
            else:
                raise

        except Exception as e:
            logging.error(f"Unexpected error: {e}")
            if attempt < max_retries - 1:
                sleep(10)
                logging.info("Retrying with fresh environment...")
            else:
                raise

    raise RuntimeError(f"EEMT calculation failed after {max_retries} attempts")

Performance Optimization

Memory Management

def optimize_grass_memory(max_memory_gb=16):
    """
    Optimize GRASS GIS memory settings for large datasets

    Parameters:
    -----------
    max_memory_gb : int
        Maximum memory to allocate [GB]
    """

    import os

    # Set GRASS memory environment
    cache_size = max_memory_gb * 1024  # Convert to MB

    os.environ.update({
        'GRASS_CACHE_SIZE': str(cache_size),
        'GRASS_RASTER_TMPDIR_MAPSET': '/tmp',
        'GRASS_VECTOR_TMPDIR_MAPSET': '/tmp',
        'GRASS_COMPRESS_NULLS': '1',
        'GRASS_RENDER_IMMEDIATE': 'FALSE'
    })

    print(f"✓ GRASS memory optimized for {max_memory_gb} GB")

def calculate_optimal_tile_size(dem_file, available_memory_gb=8):
    """
    Calculate optimal tile size for memory-constrained processing

    Parameters:
    -----------
    dem_file : str
        Path to DEM file
    available_memory_gb : int  
        Available system memory [GB]

    Returns:
    --------
    tile_size : int
        Optimal tile size in pixels
    overlap : int
        Recommended overlap in pixels
    """

    import rasterio

    with rasterio.open(dem_file) as src:
        width, height = src.width, src.height
        dtype_size = np.dtype(src.dtypes[0]).itemsize

    # Estimate memory usage per pixel (including intermediate arrays)
    memory_per_pixel = dtype_size * 20  # Factor for r.sun calculations

    # Calculate tile size that fits in available memory
    available_memory_bytes = available_memory_gb * 1024**3
    max_pixels = available_memory_bytes // memory_per_pixel
    tile_size = int(np.sqrt(max_pixels))

    # Ensure reasonable tile size
    tile_size = max(256, min(tile_size, 4096))
    overlap = max(32, tile_size // 16)  # 6.25% overlap

    return tile_size, overlap

Parallel Processing Configuration

def configure_parallel_processing(max_workers=None, threads_per_worker=4):
    """
    Configure optimal parallel processing parameters

    Parameters:
    -----------
    max_workers : int, optional
        Maximum number of worker processes (default: CPU count // 4)
    threads_per_worker : int
        OpenMP threads per worker process

    Returns:
    --------
    config : dict
        Optimized processing configuration
    """

    import multiprocessing as mp
    import psutil

    # Detect system capabilities
    cpu_count = mp.cpu_count()
    memory_gb = psutil.virtual_memory().total // (1024**3)

    # Calculate optimal configuration
    if max_workers is None:
        max_workers = max(1, cpu_count // threads_per_worker)

    # Memory per worker (reserve 2 GB for system)
    memory_per_worker = max(2, (memory_gb - 2) // max_workers)

    config = {
        'max_workers': max_workers,
        'threads_per_worker': threads_per_worker,
        'memory_per_worker_gb': memory_per_worker,
        'total_threads': max_workers * threads_per_worker,
        'memory_efficiency': memory_per_worker / (memory_gb / max_workers)
    }

    print(f"Parallel Processing Configuration:")
    print(f"  Workers: {config['max_workers']}")
    print(f"  Threads per worker: {config['threads_per_worker']}")
    print(f"  Total threads: {config['total_threads']}")
    print(f"  Memory per worker: {config['memory_per_worker_gb']} GB")

    return config

Validation Functions

Statistical Validation

def validate_eemt_results(eemt_results, validation_data, method='pearson'):
    """
    Validate EEMT results against field measurements

    Parameters:
    -----------
    eemt_results : dict
        EEMT calculation results
    validation_data : dict
        Validation datasets:
        - 'soil_depth': Measured soil depths [cm]
        - 'biomass': Measured biomass [Mg/ha]
        - 'npp': Measured NPP [kg/m²/yr] 
        - 'coordinates': Sample locations
    method : str
        Validation method: 'pearson', 'spearman', 'rmse'

    Returns:
    --------
    validation_results : dict
        Validation statistics and plots
    """

    from scipy import stats
    import matplotlib.pyplot as plt

    validation_results = {}

    # Extract EEMT values at validation points
    for data_type, data in validation_data.items():
        if data_type == 'coordinates':
            continue

        # Extract EEMT values at measurement locations
        eemt_at_points = extract_values_at_points(
            eemt_results['eemt'], 
            validation_data['coordinates']
        )

        # Calculate validation statistics
        if method == 'pearson':
            r, p = stats.pearsonr(eemt_at_points, data)
            validation_results[data_type] = {
                'correlation': r,
                'p_value': p,
                'r_squared': r**2
            }
        elif method == 'rmse':
            rmse = np.sqrt(np.mean((eemt_at_points - data)**2))
            mae = np.mean(np.abs(eemt_at_points - data))
            validation_results[data_type] = {
                'rmse': rmse,
                'mae': mae,
                'bias': np.mean(eemt_at_points - data)
            }

    return validation_results

def cross_validate_eemt_methods(dem_file, climate_data, validation_points):
    """
    Cross-validation of different EEMT calculation methods

    Compares Traditional, Topographic, and Vegetation approaches
    against field validation data
    """

    methods = ['traditional', 'topographic', 'vegetation']
    results = {}

    for method in methods:
        print(f"Cross-validating {method} EEMT...")

        # Calculate EEMT using specific method
        if method == 'traditional':
            eemt = calculate_eemt_traditional(climate_data)
        elif method == 'topographic': 
            eemt = calculate_eemt_topographic(dem_file, climate_data)
        else:
            eemt = calculate_eemt_vegetation(dem_file, climate_data)

        # Validate against field data
        validation = validate_eemt_results(eemt, validation_points)
        results[method] = validation

    # Compare methods
    print("\\nMethod Comparison:")
    print("-" * 50)
    for method, validation in results.items():
        if 'soil_depth' in validation:
            r2 = validation['soil_depth']['r_squared']
            print(f"{method.capitalize():12} | R² = {r2:.3f}")

    return results

Utility Functions

Data Processing Utilities

def extract_values_at_points(raster_file, coordinates, method='bilinear'):
    """Extract raster values at point locations"""

    import rasterio
    from rasterio.sample import sample_gen

    with rasterio.open(raster_file) as src:
        values = list(sample_gen(src, coordinates, indexes=1))

    return np.array([val[0] for val in values])

def calculate_zonal_statistics(raster_file, zones_file, statistics=['mean', 'std']):
    """Calculate statistics by zones (e.g., elevation bands, watersheds)"""

    from rasterstats import zonal_stats
    import geopandas as gpd

    # Load zones
    zones = gpd.read_file(zones_file)

    # Calculate statistics
    stats_result = zonal_stats(
        zones, 
        raster_file, 
        stats=statistics,
        geojson_out=True
    )

    return gpd.GeoDataFrame.from_features(stats_result)

def resample_to_common_grid(file_list, reference_file, output_dir, method='bilinear'):
    """Resample all rasters to common grid"""

    import subprocess
    from pathlib import Path

    output_dir = Path(output_dir)
    output_dir.mkdir(exist_ok=True)

    # Get reference grid parameters
    with rasterio.open(reference_file) as src:
        ref_transform = src.transform
        ref_crs = src.crs
        ref_width = src.width
        ref_height = src.height

    resampled_files = []

    for input_file in file_list:

        output_file = output_dir / f"resampled_{Path(input_file).name}"

        # Use gdalwarp for resampling
        cmd = [
            'gdalwarp',
            '-tr', str(ref_transform[0]), str(-ref_transform[4]),  # Resolution
            '-te', str(ref_transform[2]), str(ref_transform[5]),   # Extent  
                   str(ref_transform[2] + ref_width * ref_transform[0]),
                   str(ref_transform[5] + ref_height * ref_transform[4]),
            '-t_srs', str(ref_crs),  # Target CRS
            '-r', method,            # Resampling method
            '-co', 'COMPRESS=LZW',   # Compression
            str(input_file),
            str(output_file)
        ]

        subprocess.run(cmd, check=True)
        resampled_files.append(str(output_file))

    return resampled_files

Command Line Interface

Main EEMT Calculator Script

#!/usr/bin/env python3
"""
Command line interface for EEMT calculations
Usage: python eemt_calculator.py [options] dem_file
"""

usage_examples = '''
Examples:
  # Basic EEMT calculation
  python eemt_calculator.py dem.tif --climate climate_data/ --output results/

  # Topographic EEMT with parallel processing
  python eemt_calculator.py dem.tif --method topographic --threads 16 \\
    --climate daymet_data/ --output topo_results/

  # Full vegetation EEMT with validation
  python eemt_calculator.py dem.tif --method vegetation \\
    --climate climate/ --vegetation ndvi.tif,lidar.las \\
    --validate soil_samples.shp --output veg_results/

  # Time series analysis
  python eemt_calculator.py dem.tif --method topographic \\
    --start-year 2000 --end-year 2020 --time-series \\
    --output timeseries_results/
'''

# Command line argument definitions
CLI_ARGUMENTS = {
    'dem_file': {
        'type': str,
        'help': 'Input digital elevation model (GeoTIFF format)'
    },
    '--method': {
        'choices': ['traditional', 'topographic', 'vegetation', 'all'],
        'default': 'topographic',
        'help': 'EEMT calculation method'
    },
    '--climate': {
        'type': str, 
        'required': True,
        'help': 'Climate data directory (DAYMET NetCDF files)'
    },
    '--output': {
        'type': str,
        'required': True, 
        'help': 'Output directory for results'
    },
    '--threads': {
        'type': int,
        'default': 4,
        'help': 'Number of parallel processing threads'
    },
    '--step': {
        'type': float,
        'default': 0.25,
        'help': 'Solar calculation time step [hours]'
    },
    '--linke': {
        'type': float,
        'default': 3.0,
        'help': 'Linke atmospheric turbidity factor [1.0-8.0]'
    },
    '--albedo': {
        'type': float, 
        'default': 0.2,
        'help': 'Surface albedo coefficient [0.0-1.0]'
    },
    '--vegetation': {
        'type': str,
        'help': 'Vegetation data files (comma-separated): ndvi.tif,lidar.las'
    },
    '--start-year': {
        'type': int,
        'default': 2015,
        'help': 'Start year for time series analysis'
    },
    '--end-year': {
        'type': int, 
        'default': 2020,
        'help': 'End year for time series analysis'
    },
    '--validate': {
        'type': str,
        'help': 'Validation data file (point shapefile with measurements)'
    },
    '--time-series': {
        'action': 'store_true',
        'help': 'Generate annual time series output'
    },
    '--tile-size': {
        'type': int,
        'default': 2048,
        'help': 'Tile size for large dataset processing [pixels]'
    },
    '--verbose': {
        'action': 'store_true',
        'help': 'Enable verbose output'
    }
}

Testing Framework

Unit Tests

import unittest
import numpy as np
from pathlib import Path

class TestEEMTCalculations(unittest.TestCase):
    """Unit tests for EEMT calculation functions"""

    def setUp(self):
        """Set up test data"""
        self.test_data_dir = Path('test_data')
        self.test_data_dir.mkdir(exist_ok=True)

        # Create synthetic test DEM
        self.create_test_dem()

        # Create synthetic climate data
        self.create_test_climate()

    def test_traditional_eemt(self):
        """Test traditional EEMT calculation"""

        # Simple test case
        temp = np.array([15.0])  # °C
        precip = np.array([50.0])  # mm/month

        eemt, e_bio, e_ppt = calculate_eemt_traditional(temp, precip)

        # Check output ranges
        self.assertGreater(eemt[0], 0, "EEMT should be positive")
        self.assertLess(eemt[0], 100, "EEMT should be reasonable (<100 MJ/m²/yr)")

        # Check components
        self.assertGreater(e_bio[0], 0, "E_BIO should be positive")
        self.assertGreaterEqual(e_ppt[0], 0, "E_PPT should be non-negative")

    def test_solar_radiation_range(self):
        """Test solar radiation calculations produce reasonable values"""

        # Test with synthetic DEM
        dem_file = self.test_data_dir / 'test_dem.tif'

        # Should complete without errors
        try:
            solar_result = calculate_annual_solar(dem_file, days=[180])  # Summer solstice
            self.assertTrue(True, "Solar calculation completed")
        except Exception as e:
            self.fail(f"Solar calculation failed: {e}")

    def test_aspect_effects(self):
        """Test that north-facing slopes have higher EEMT"""

        # Create test data with known aspect effects
        north_slope_eemt = calculate_eemt_topographic(
            self.create_test_slope(aspect=0)  # North-facing
        )

        south_slope_eemt = calculate_eemt_topographic(
            self.create_test_slope(aspect=180)  # South-facing
        )

        # North slopes should have higher EEMT in water-limited environments
        self.assertGreater(
            np.mean(north_slope_eemt),
            np.mean(south_slope_eemt),
            "North-facing slopes should have higher EEMT"
        )

    def create_test_dem(self):
        """Create synthetic DEM for testing"""

        # Create elevation gradient
        x, y = np.meshgrid(np.linspace(0, 1000, 100), np.linspace(0, 1000, 100))
        elevation = 1000 + x * 0.5 + y * 0.3 + np.random.normal(0, 10, (100, 100))

        # Save as GeoTIFF
        profile = {
            'driver': 'GTiff',
            'height': 100,
            'width': 100,
            'count': 1,
            'dtype': 'float32',
            'crs': 'EPSG:4326',
            'transform': rasterio.transform.from_bounds(-111, 32, -110, 33, 100, 100)
        }

        with rasterio.open(self.test_data_dir / 'test_dem.tif', 'w', **profile) as dst:
            dst.write(elevation.astype(np.float32), 1)

if __name__ == '__main__':
    unittest.main()

Integration Tests

#!/bin/bash
# Integration test suite for EEMT workflows

set -e

echo "=== EEMT Integration Tests ==="

# Test 1: Basic workflow with sample data
echo "Test 1: Basic EEMT workflow..."
python eemt_calculator.py test_data/sample_dem.tif \\
  --climate test_data/climate/ \\
  --output test_results/basic/ \\
  --method traditional

# Test 2: Parallel processing
echo "Test 2: Parallel solar calculation..."
python eemt_calculator.py test_data/sample_dem.tif \\
  --climate test_data/climate/ \\
  --output test_results/parallel/ \\
  --method topographic \\
  --threads 4

# Test 3: Large dataset handling
echo "Test 3: Large dataset processing..."
python eemt_calculator.py test_data/large_dem.tif \\
  --climate test_data/climate/ \\
  --output test_results/large/ \\
  --tile-size 1024

# Test 4: Validation
echo "Test 4: Results validation..."
python validate_results.py test_results/ test_data/validation/

echo "✓ All integration tests passed"

This API reference provides the complete technical foundation for implementing and extending EEMT calculations with modern computational approaches and comprehensive error handling.