Source code for openeurotop.validation

"""
Module de validation et vérifications selon EurOtop (2018)

Vérifie les domaines de validité, signale les warnings et propose des recommandations
"""

import numpy as np
from openeurotop import wave_parameters
from openeurotop.constants import (
    MIN_SLOPE_ANGLE, MAX_SLOPE_ANGLE,
    MAX_RC_HM0, MIN_RC_HM0
)


[docs] class ValidationResult: """Classe pour stocker les résultats de validation"""
[docs] def __init__(self): self.is_valid = True self.warnings = [] self.errors = [] self.recommendations = [] self.parameters = {}
[docs] def add_warning(self, message): """Ajoute un avertissement""" self.warnings.append(message)
[docs] def add_error(self, message): """Ajoute une erreur (invalidité)""" self.errors.append(message) self.is_valid = False
[docs] def add_recommendation(self, message): """Ajoute une recommandation""" self.recommendations.append(message)
[docs] def __str__(self): """Affichage formaté des résultats""" lines = [] lines.append("="*70) lines.append("RÉSULTATS DE VALIDATION") lines.append("="*70) if self.is_valid: lines.append("✓ VALIDITÉ : OK") else: lines.append("✗ VALIDITÉ : HORS DOMAINE") if self.errors: lines.append("\n❌ ERREURS :") for err in self.errors: lines.append(f" • {err}") if self.warnings: lines.append("\n⚠️ AVERTISSEMENTS :") for warn in self.warnings: lines.append(f" • {warn}") if self.recommendations: lines.append("\n💡 RECOMMANDATIONS :") for rec in self.recommendations: lines.append(f" • {rec}") if self.parameters: lines.append("\n📊 PARAMÈTRES CALCULÉS :") for key, value in self.parameters.items(): if isinstance(value, float): lines.append(f" • {key} = {value:.3f}") else: lines.append(f" • {key} = {value}") lines.append("="*70) return "\n".join(lines)
[docs] def validate_slope_structure(Hm0, Tm_10, h, Rc, alpha_deg, gamma_f=1.0, gamma_beta=1.0): """ Valide les paramètres pour une structure à talus Vérifie les domaines de validité selon EurOtop 2018 Parameters ---------- Hm0, Tm_10, h, Rc, alpha_deg : float Paramètres de la structure gamma_f, gamma_beta : float Facteurs de réduction Returns ------- ValidationResult Objet contenant les résultats de validation Examples -------- >>> result = validate_slope_structure(2.5, 6.0, 10.0, 3.0, 35.0) >>> print(result) >>> if result.is_valid: ... print("OK pour calcul") """ result = ValidationResult() # 1. Vérifier la revanche relative Rc_Hm0 = Rc / Hm0 result.parameters['Rc/Hm0'] = Rc_Hm0 if Rc_Hm0 < MIN_RC_HM0: result.add_error( f"Rc/Hm0 = {Rc_Hm0:.2f} < {MIN_RC_HM0} : Hors domaine de validité. " f"La structure est submergée ou quasi-submergée." ) result.add_recommendation( "Pour Rc/Hm0 < 0.5, les formules de franchissement sont très incertaines. " "Considérer une conception différente ou des essais physiques." ) elif Rc_Hm0 > MAX_RC_HM0: result.add_warning( f"Rc/Hm0 = {Rc_Hm0:.2f} > {MAX_RC_HM0} : Extrapolation au-delà du domaine validé. " f"Les incertitudes sont plus élevées." ) result.add_recommendation( "Pour Rc/Hm0 > 3.5, le franchissement est généralement très faible. " "Les formules restent conservatrices." ) # 2. Vérifier l'angle de pente result.parameters['alpha (deg)'] = alpha_deg if alpha_deg < MIN_SLOPE_ANGLE: result.add_warning( f"Pente α = {alpha_deg}° < {MIN_SLOPE_ANGLE}° : Pente très douce. " f"Les formules standards sont moins fiables." ) result.add_recommendation( "Pour α < 10°, utiliser openeurotop.special_cases.very_gentle_slope() " "qui applique une correction empirique." ) elif alpha_deg > 60: result.add_warning( f"Pente α = {alpha_deg}° > 60° : Pente très raide. " f"Le comportement se rapproche d'un mur vertical." ) result.add_recommendation( "Pour α > 60°, utiliser openeurotop.special_cases.very_steep_slope() " "qui interpole vers un mur vertical." ) # 3. Vérifier le nombre d'Iribarren xi = wave_parameters.iribarren_number(alpha_deg, Hm0, Tm_10) result.parameters['xi (Iribarren)'] = xi if xi < 0.5: result.add_warning( f"Nombre d'Iribarren ξ = {xi:.2f} < 0.5 : Vagues très fortement déferlantes. " f"Cas rare en pratique." ) elif xi > 7.0: result.add_warning( f"Nombre d'Iribarren ξ = {xi:.2f} > 7.0 : Vagues très non-déferlantes. " f"Configuration peu courante." ) # 4. Vérifier la profondeur relative h_Hm0 = h / Hm0 result.parameters['h/Hm0'] = h_Hm0 if h_Hm0 < 2.0: result.add_warning( f"Profondeur relative h/Hm0 = {h_Hm0:.2f} < 2.0 : Eau peu profonde. " f"Déferlement probable avant la structure." ) result.add_recommendation( "En eau peu profonde, considérer la correction avec " "openeurotop.special_cases.shallow_water_correction()" ) # 5. Vérifier la cambrure s0 = wave_parameters.wave_steepness(Hm0, Tm_10) result.parameters['s0 (steepness)'] = s0 if s0 < 0.005: result.add_warning( f"Cambrure s0 = {s0:.4f} < 0.005 : Vagues très plates (houle longue). " f"Configuration inhabituelle." ) elif s0 > 0.07: result.add_warning( f"Cambrure s0 = {s0:.4f} > 0.07 : Vagues très cambrées (mer du vent). " f"Vagues potentiellement déferlantes." ) # 6. Vérifier les facteurs de réduction if gamma_f < 0.3 or gamma_f > 1.0: result.add_warning( f"Facteur de rugosité γf = {gamma_f:.2f} hors plage typique [0.3, 1.0]" ) if gamma_beta < 0.5 or gamma_beta > 1.0: result.add_warning( f"Facteur d'obliquité γβ = {gamma_beta:.2f} hors plage typique [0.5, 1.0]" ) # 7. Recommandations générales if result.is_valid and len(result.warnings) == 0: result.add_recommendation( "Tous les paramètres sont dans le domaine de validité standard. " "Les formules EurOtop peuvent être appliquées avec confiance." ) return result
[docs] def validate_vertical_wall(Hm0, Tm_10, h, Rc): """ Valide les paramètres pour un mur vertical Parameters ---------- Hm0, Tm_10, h, Rc : float Paramètres de la structure Returns ------- ValidationResult Résultats de validation """ result = ValidationResult() # 1. Revanche relative Rc_Hm0 = Rc / Hm0 result.parameters['Rc/Hm0'] = Rc_Hm0 if Rc_Hm0 < 0.1: result.add_error( f"Rc/Hm0 = {Rc_Hm0:.2f} < 0.1 : Hors domaine de validité pour mur vertical. " f"La structure est submergée." ) elif Rc_Hm0 > 3.5: result.add_warning( f"Rc/Hm0 = {Rc_Hm0:.2f} > 3.5 : Extrapolation. " f"Le franchissement devrait être très faible." ) # 2. Profondeur relative d_star = h / Hm0 result.parameters['h/Hm0'] = d_star if d_star < 0.2: result.add_warning( f"h/Hm0 = {d_star:.2f} < 0.2 : Très peu profond. " f"Conditions potentiellement impulsives." ) result.add_recommendation( "Pour h/Hm0 < 0.3, les impacts peuvent être impulsifs. " "Vérifier avec impulsive=True dans mur_vertical()" ) elif d_star > 5.0: result.add_warning( f"h/Hm0 = {d_star:.2f} > 5.0 : Eau très profonde. " f"Configuration inhabituelle pour un mur de protection." ) # 3. Période et longueur d'onde L0 = wave_parameters.wave_length_deep_water(Tm_10) result.parameters['L0 (m)'] = L0 h_L0 = h / L0 result.parameters['h/L0'] = h_L0 if h_L0 < 0.1: result.add_warning( f"h/L0 = {h_L0:.2f} < 0.1 : Eau peu profonde par rapport à la longueur d'onde. " f"Les vagues peuvent avoir déferlé." ) return result
[docs] def validate_composite_structure(Hm0, Tm_10, h, Rc, alpha_lower_deg, h_transition): """ Valide les paramètres pour une structure composite Parameters ---------- Hm0, Tm_10, h, Rc : float Paramètres de vague alpha_lower_deg : float Angle de pente inférieure h_transition : float Hauteur de transition Returns ------- ValidationResult Résultats de validation """ result = ValidationResult() # 1. Valider la partie talus result_slope = validate_slope_structure(Hm0, Tm_10, h, h_transition, alpha_lower_deg) result.warnings.extend(result_slope.warnings) result.errors.extend(result_slope.errors) # 2. Vérifier la cohérence de la transition if h_transition <= 0: result.add_error( f"Hauteur de transition h_transition = {h_transition:.2f} m <= 0. " f"La transition doit être au-dessus du SWL." ) elif h_transition >= Rc: result.add_warning( f"Hauteur de transition {h_transition:.2f} m >= Revanche {Rc:.2f} m. " f"Le mur vertical n'a pas d'effet." ) result.add_recommendation( "Si h_transition >= Rc, utiliser simplement une formule pour talus simple." ) # 3. Estimer le run-up from openeurotop.run_up import run_up_2percent_smooth_slope Ru2_estimate = run_up_2percent_smooth_slope(Hm0, Tm_10, alpha_lower_deg) result.parameters['Ru2% estimé (m)'] = Ru2_estimate if h_transition > Ru2_estimate: result.add_warning( f"La transition ({h_transition:.2f} m) est au-dessus du run-up estimé " f"({Ru2_estimate:.2f} m). Le mur vertical sera peu sollicité." ) return result
[docs] def check_design_requirements(q, q_limit, safety_factor=1.0): """ Vérifie si le débit respecte les critères de conception Parameters ---------- q : float Débit calculé (m³/s/m) q_limit : float Débit limite acceptable (m³/s/m) safety_factor : float, optional Facteur de sécurité à appliquer Returns ------- dict Résultats de vérification Examples -------- >>> # Vérifier si q < 1 l/s/m avec facteur de sécurité 1.5 >>> check = check_design_requirements(0.0008, 0.001, safety_factor=1.5) >>> if check['acceptable']: ... print("Conception acceptable") """ q_design = q * safety_factor acceptable = q_design <= q_limit margin = (q_limit - q_design) / q_limit * 100 if acceptable else None exceedance = (q_design - q_limit) / q_limit * 100 if not acceptable else None return { 'q_calculated': q, 'q_design': q_design, 'q_limit': q_limit, 'safety_factor': safety_factor, 'acceptable': acceptable, 'margin_percent': margin, 'exceedance_percent': exceedance, 'status': 'OK' if acceptable else 'DÉPASSEMENT' }
[docs] def validate_all_parameters(structure_type, **params): """ Validation globale pour tous types de structures Parameters ---------- structure_type : str Type de structure : "slope", "vertical_wall", "composite" **params : dict Paramètres de la structure Returns ------- ValidationResult Résultats complets de validation Examples -------- >>> result = validate_all_parameters( ... "slope", ... Hm0=2.5, Tm_10=6.0, h=10.0, Rc=3.0, alpha_deg=35.0 ... ) >>> print(result) """ if structure_type == "slope": required = ['Hm0', 'Tm_10', 'h', 'Rc', 'alpha_deg'] for param in required: if param not in params: raise ValueError(f"Paramètre manquant : {param}") return validate_slope_structure( params['Hm0'], params['Tm_10'], params['h'], params['Rc'], params['alpha_deg'], params.get('gamma_f', 1.0), params.get('gamma_beta', 1.0) ) elif structure_type == "vertical_wall": required = ['Hm0', 'Tm_10', 'h', 'Rc'] for param in required: if param not in params: raise ValueError(f"Paramètre manquant : {param}") return validate_vertical_wall( params['Hm0'], params['Tm_10'], params['h'], params['Rc'] ) elif structure_type == "composite": required = ['Hm0', 'Tm_10', 'h', 'Rc', 'alpha_lower_deg', 'h_transition'] for param in required: if param not in params: raise ValueError(f"Paramètre manquant : {param}") return validate_composite_structure( params['Hm0'], params['Tm_10'], params['h'], params['Rc'], params['alpha_lower_deg'], params['h_transition'] ) else: raise ValueError(f"Type de structure inconnu : {structure_type}")
[docs] def generate_validation_report(structure_type, calculation_results, **params): """ Génère un rapport de validation complet Parameters ---------- structure_type : str Type de structure calculation_results : dict Résultats de calcul (débit, etc.) **params : dict Paramètres de la structure Returns ------- str Rapport formaté Examples -------- >>> from openeurotop import overtopping >>> q = overtopping.digue_talus(2.5, 6.0, 10.0, 3.0, 35.0) >>> report = generate_validation_report( ... "slope", ... {'q': q}, ... Hm0=2.5, Tm_10=6.0, h=10.0, Rc=3.0, alpha_deg=35.0 ... ) >>> print(report) """ # Validation des paramètres validation = validate_all_parameters(structure_type, **params) # Construction du rapport lines = [] lines.append("="*80) lines.append("RAPPORT DE VALIDATION - OPENEUROTOP") lines.append("="*80) lines.append(f"\nType de structure : {structure_type}") lines.append(f"\nDate : {np.datetime64('today')}") lines.append("\n" + "-"*80) lines.append("PARAMÈTRES D'ENTRÉE") lines.append("-"*80) for key, value in sorted(params.items()): if isinstance(value, float): lines.append(f" {key:<20} = {value:>10.3f}") else: lines.append(f" {key:<20} = {value:>10}") lines.append("\n" + "-"*80) lines.append("RÉSULTATS DE CALCUL") lines.append("-"*80) for key, value in sorted(calculation_results.items()): if isinstance(value, float): if 'q' in key.lower(): lines.append(f" {key:<20} = {value:>10.6f} m³/s/m = {value*1000:>10.3f} l/s/m") else: lines.append(f" {key:<20} = {value:>10.3f}") else: lines.append(f" {key:<20} = {value}") lines.append("\n" + str(validation)) lines.append("\n" + "="*80) lines.append("FIN DU RAPPORT") lines.append("="*80) return "\n".join(lines)