Source code for analysis.analyze_forecast_lambda

#!/usr/bin/env python3
"""
Analyze Lambda sum of PPE and EEPAS Forecast results

Based on previous discussions, the forecast matrix format:
- Shape: (n_windows × n_mag_bins, n_cells + 1)
- Each element represents the predicted seismicity rate for that spatio-temporal-magnitude bin
- Need to sum all elements to obtain the total predicted earthquake count
"""

import scipy.io as sio
import numpy as np
import json

[docs] def analyze_forecast_results(results_dir, config_file): """ Analyze PPE and EEPAS forecast Lambda sum for validation. This function validates forecast results by checking if the integrated rate density (Lambda sum) is consistent with the observed event count. For a properly calibrated forecast, the Lambda sum should be close to the number of observed events in the forecast period. Args: results_dir: Path to results directory containing forecast .mat files config_file: Path to configuration JSON file Returns: dict: Dictionary containing 'ppe_lambda_sum' and 'eepas_lambda_sum' Notes: - Forecast matrix format: (n_windows × n_mag_bins, n_cells + 1) - First column contains time_s index and should be excluded from Lambda sum - Lambda sum = Σ forecast[:, 1:] (exclude index column) - Expected: Lambda sum ≈ observed event count (within ±20%) """ print(f"\n{'='*70}") print(f"Analyzing Forecast results: {results_dir}") print(f"{'='*70}") # Load configuration with open(config_file, 'r') as f: config = json.load(f) learn_start = config['learnStartYear'] learn_end = config['learnEndYear'] forecast_start = config['forecastStartYear'] forecast_end = config['forecastEndYear'] print(f"\nConfiguration:") print(f" Learning period: {learn_start} - {learn_end}") print(f" Forecast period: {forecast_start} - {forecast_end}") # Load PPE Forecast try: ppe_file = f"{results_dir}/PREVISIONI_3m_PPE_{forecast_start}_{forecast_end}.mat" mat_ppe = sio.loadmat(ppe_file) ppe_forecast = mat_ppe['PREVISIONI_3m'] print(f"\n[PPE Forecast]") print(f" File: {ppe_file}") print(f" Matrix shape: {ppe_forecast.shape}") print(f" Matrix element sum: {np.sum(ppe_forecast):.6f}") # Analyze matrix structure n_rows, n_cols = ppe_forecast.shape print(f"\n Matrix structure analysis:") print(f" Rows (time windows × magnitude bins): {n_rows}") print(f" Columns (grid cells + 1): {n_cols}") # Calculate column sums col_sums = np.sum(ppe_forecast, axis=0) print(f" First column sum (time_s index): {col_sums[0]:.6f}") print(f" Remaining {n_cols-1} columns sum (actual grid data): {np.sum(col_sums[1:]):.6f}") # Lambda sum: exclude first column (time_s index) ppe_lambda_sum = np.sum(ppe_forecast[:, 1:]) except Exception as e: print(f"\nError: Cannot read PPE Forecast - {e}") ppe_lambda_sum = None # Load EEPAS Forecast try: eepas_file = f"{results_dir}/PREVISIONI_3m_EEPAS_{forecast_start}_{forecast_end}.mat" mat_eepas = sio.loadmat(eepas_file) eepas_forecast = mat_eepas['PREVISIONI_3m_less'] print(f"\n[EEPAS Forecast]") print(f" File: {eepas_file}") print(f" Matrix shape: {eepas_forecast.shape}") print(f" Matrix element sum: {np.sum(eepas_forecast):.6f}") # Analyze matrix structure n_rows, n_cols = eepas_forecast.shape print(f"\n Matrix structure analysis:") print(f" Rows (time windows × magnitude bins): {n_rows}") print(f" Columns (grid cells + 1): {n_cols}") # Calculate column sums col_sums = np.sum(eepas_forecast, axis=0) print(f" First column sum (time_s index): {col_sums[0]:.6f}") print(f" Remaining {n_cols-1} columns sum (actual grid data): {np.sum(col_sums[1:]):.6f}") # Lambda sum: exclude first column (time_s index) eepas_lambda_sum = np.sum(eepas_forecast[:, 1:]) except Exception as e: print(f"\nError: Cannot read EEPAS Forecast - {e}") eepas_lambda_sum = None # Summary print(f"\n{'='*70}") print(f"Lambda Sum Summary") print(f"{'='*70}") if ppe_lambda_sum is not None: print(f"\nPPE Lambda sum: {ppe_lambda_sum:.6f}") if eepas_lambda_sum is not None: print(f"EEPAS Lambda sum: {eepas_lambda_sum:.6f}") if ppe_lambda_sum is not None and eepas_lambda_sum is not None: diff = abs(eepas_lambda_sum - ppe_lambda_sum) rel_diff = diff / ppe_lambda_sum * 100 print(f"\nDifference: {diff:.6f} ({rel_diff:.4f}%)") # Check if close to target event count # Note: This sum represents the entire forecast period # If the forecast period is T years and predicts N events per year, the sum should be close to N*T # Or if forecasting the learning period, should be close to the learning period event count print(f"\n⚠️ Note: These values represent the sum of the entire forecast matrix") print(f" To verify if close to actual event count, need to:") print(f" 1. Confirm forecast period and target event count") print(f" 2. Consider spatio-temporal-magnitude resolution of the matrix") print(f" 3. May need to sum over specific time windows or magnitude ranges") return { 'ppe_lambda_sum': ppe_lambda_sum, 'eepas_lambda_sum': eepas_lambda_sum }
if __name__ == "__main__": import sys print("\n" + "="*70) print("PPE and EEPAS Forecast Lambda Sum Analysis") print("="*70) # Default: analyze causal EW results print("\n\n" + "="*70) print("[useCausalEW=0 Results]") print("="*70) results_ew0 = analyze_forecast_results( "results_italy_causal_ew0", "config_italy_causal_ew0.json" ) print("\n\n" + "="*70) print("[useCausalEW=1 Results]") print("="*70) results_ew1 = analyze_forecast_results( "results_italy_causal_ew1", "config_italy_causal_ew1.json" ) # Compare two modes if results_ew0['ppe_lambda_sum'] and results_ew1['ppe_lambda_sum']: print("\n\n" + "="*70) print("[useCausalEW=0 vs useCausalEW=1]") print("="*70) print(f"\nPPE Lambda sum:") print(f" useCausalEW=0: {results_ew0['ppe_lambda_sum']:.6f}") print(f" useCausalEW=1: {results_ew1['ppe_lambda_sum']:.6f}") diff = abs(results_ew0['ppe_lambda_sum'] - results_ew1['ppe_lambda_sum']) rel_diff = diff / results_ew0['ppe_lambda_sum'] * 100 print(f" Absolute difference: {diff:.6f}") print(f" Relative difference: {rel_diff:.4f}%") print(f"\nEEPAS Lambda sum:") print(f" useCausalEW=0: {results_ew0['eepas_lambda_sum']:.6f}") print(f" useCausalEW=1: {results_ew1['eepas_lambda_sum']:.6f}") diff = abs(results_ew0['eepas_lambda_sum'] - results_ew1['eepas_lambda_sum']) rel_diff = diff / results_ew0['eepas_lambda_sum'] * 100 print(f" Absolute difference: {diff:.6f}") print(f" Relative difference: {rel_diff:.4f}%") # Explain forecast period print("\n\n" + "="*70) print("[Forecast Results Explanation]") print("="*70) print(f"\nNote: Learning period is 1990-2012 (22 years), forecast period is 2012-2022 (10 years)") print(f" Lambda sum represents expected earthquake count for forecast period (2012-2022)") print(f"\nPPE Lambda sum:") print(f" useCausalEW=0: {results_ew0['ppe_lambda_sum']:.2f}") print(f" useCausalEW=1: {results_ew1['ppe_lambda_sum']:.2f}") print(f"\nEEPAS Lambda sum:") print(f" useCausalEW=0: {results_ew0['eepas_lambda_sum']:.2f}") print(f" useCausalEW=1: {results_ew1['eepas_lambda_sum']:.2f}")