Source code for homodyne.optimization.nlsq.validation.result_validator

"""Result validation for NLSQ optimization (T080).

Extracted from wrapper.py as part of architecture refactoring.
"""

from __future__ import annotations

import numpy as np

from homodyne.utils.logging import get_logger

logger = get_logger(__name__)


[docs] class ResultValidator: """Validator for NLSQ optimization results. Validates optimized parameters, covariance matrices, and result consistency. """
[docs] def __init__(self, strict_mode: bool = False): """Initialize ResultValidator. Parameters ---------- strict_mode : bool, optional If True, raise errors on validation failures. If False, log warnings but continue. """ self.strict_mode = strict_mode self._validation_warnings: list[str] = []
[docs] def validate_all( self, params: np.ndarray, covariance: np.ndarray | None, bounds: tuple[np.ndarray, np.ndarray] | None, chi_squared: float | None = None, ) -> bool: """Validate all result components. Parameters ---------- params : np.ndarray Optimized parameter values covariance : np.ndarray | None Parameter covariance matrix bounds : tuple[np.ndarray, np.ndarray] | None Parameter bounds (lower, upper) chi_squared : float | None, optional Chi-squared value for quality check Returns ------- bool True if all validation passes, False otherwise """ self._validation_warnings = [] # Validate optimized params if not validate_optimized_params(params, bounds): self._validation_warnings.append("Optimized parameters outside bounds") # Validate covariance if covariance is not None: if not validate_covariance(covariance, len(params)): self._validation_warnings.append("Covariance matrix invalid") # Check result consistency if chi_squared is not None: if not validate_result_consistency(params, chi_squared): self._validation_warnings.append("Result consistency check failed") if self._validation_warnings: if self.strict_mode: raise ValueError( f"Result validation failed: {'; '.join(self._validation_warnings)}" ) else: for warning in self._validation_warnings: logger.warning(f"Result validation warning: {warning}") return False return True
@property def validation_warnings(self) -> list[str]: """Get list of validation warnings from last validate_all() call.""" return self._validation_warnings.copy()
[docs] def validate_optimized_params( params: np.ndarray, bounds: tuple[np.ndarray, np.ndarray] | None, tolerance: float = 1e-10, ) -> bool: """Validate that optimized parameters are finite and within bounds. Parameters ---------- params : np.ndarray Optimized parameter values bounds : tuple[np.ndarray, np.ndarray] | None (lower, upper) bounds arrays tolerance : float, optional Tolerance for boundary violations Returns ------- bool True if params are valid """ # Check for NaN/Inf if not np.all(np.isfinite(params)): nan_count = np.sum(np.isnan(params)) inf_count = np.sum(np.isinf(params)) logger.warning(f"Optimized params contain {nan_count} NaN, {inf_count} Inf") return False if bounds is None: return True lower, upper = bounds # Check bounds with tolerance below_lower = params < (lower - tolerance) above_upper = params > (upper + tolerance) if np.any(below_lower) or np.any(above_upper): violations = [] if np.any(below_lower): indices = np.where(below_lower)[0] violations.append(f"below lower at {indices.tolist()}") if np.any(above_upper): indices = np.where(above_upper)[0] violations.append(f"above upper at {indices.tolist()}") logger.warning(f"Params outside bounds: {', '.join(violations)}") return False return True
[docs] def validate_covariance(covariance: np.ndarray, n_params: int) -> bool: """Validate covariance matrix properties. Parameters ---------- covariance : np.ndarray Parameter covariance matrix n_params : int Expected number of parameters Returns ------- bool True if covariance is valid """ # Check shape if covariance.shape != (n_params, n_params): logger.warning( f"Covariance shape {covariance.shape} != expected ({n_params}, {n_params})" ) return False # Check for NaN/Inf if not np.all(np.isfinite(covariance)): nan_count = np.sum(np.isnan(covariance)) inf_count = np.sum(np.isinf(covariance)) logger.warning(f"Covariance contains {nan_count} NaN, {inf_count} Inf") return False # Check symmetry (with tolerance for numerical errors) if not np.allclose(covariance, covariance.T, rtol=1e-8, atol=1e-10): max_diff = np.nanmax(np.abs(covariance - covariance.T)) logger.warning(f"Covariance not symmetric, max diff={max_diff:.2e}") return False # Check positive semi-definiteness (diagonal elements should be non-negative) diag = np.diag(covariance) if np.any(diag < 0): neg_indices = np.where(diag < 0)[0] logger.warning( f"Covariance has negative diagonal at indices: {neg_indices.tolist()}" ) return False return True
[docs] def validate_result_consistency( params: np.ndarray, chi_squared: float, ) -> bool: """Validate consistency of optimization result. Parameters ---------- params : np.ndarray Optimized parameter values chi_squared : float Chi-squared value Returns ------- bool True if result is consistent """ # Check chi-squared is finite and non-negative if not np.isfinite(chi_squared): logger.warning(f"Chi-squared is not finite: {chi_squared}") return False if chi_squared < 0: logger.warning(f"Chi-squared is negative: {chi_squared}") return False # Warn if chi-squared is suspiciously low (might indicate overfitting) if chi_squared < 1e-15: logger.warning(f"Chi-squared suspiciously low: {chi_squared:.2e}") # Warn if chi-squared is very high (might indicate poor fit) if chi_squared > 1e10: logger.warning(f"Chi-squared very high: {chi_squared:.2e}") return True
__all__ = [ "ResultValidator", "validate_covariance", "validate_optimized_params", "validate_result_consistency", ]