"""Shoreline Boundary Stress Parameter (SBSP) Estimates inundation momentum flux and bore formation at the shoreline transition. SBSP = Fr² · (H/H_ref) = (u²·H) / (g·H_ref²) """ import numpy as np class ShorelineBoundaryStressParameter: """ Shoreline Boundary Stress Parameter - Parameter 6 of 7 Thresholds: SAFE: SBSP < 0.3 (low inundation stress) MONITOR: 0.3 ≤ SBSP < 0.7 (moderate inundation) ALERT: 0.7 ≤ SBSP < 1.2 (high inundation) CRITICAL: SBSP ≥ 1.2 (supercritical bore) Run-up regression: R = 19.7 × SBSP − 2.1 [m] Pearson correlation r = 0.956 """ def __init__(self, g=9.81, H_ref=1.0): self.g = g self.H_ref = H_ref # reference depth for normalization self.thresholds = { 'safe': 0.3, 'monitor': 0.7, 'alert': 1.2, 'critical': 1.2 } def compute_froude(self, u, H): """Compute Froude number Fr = u/√(gH) Fr < 1: subcritical flow Fr = 1: critical flow Fr > 1: supercritical flow (bore) Args: u: flow velocity [m/s] H: water depth [m] Returns: Fr: Froude number """ return u / np.sqrt(self.g * H) def compute_sbsp(self, u, H): """Compute Shoreline Boundary Stress Parameter SBSP = Fr² · (H/H_ref) = (u²·H) / (g·H_ref²) Args: u: flow velocity at shoreline [m/s] H: water depth at shoreline [m] Returns: dict with SBSP value, status, and components """ Fr = self.compute_froude(u, H) sbsp = (u**2 * H) / (self.g * self.H_ref**2) return { 'value': float(sbsp), 'status': self.get_status(sbsp), 'froude': float(Fr), 'flow_regime': self.get_flow_regime(Fr), 'velocity': float(u), 'depth': float(H) } def get_status(self, sbsp): """Get alert status based on SBSP value""" if sbsp < self.thresholds['safe']: return 'SAFE' elif sbsp < self.thresholds['monitor']: return 'MONITOR' elif sbsp < self.thresholds['alert']: return 'ALERT' else: return 'CRITICAL' def get_flow_regime(self, Fr): """Get descriptive flow regime""" if Fr < 0.8: return "Subcritical flow" elif Fr < 1.0: return "Near-critical flow" elif Fr < 1.5: return "Supercritical flow (bore)" else: return "Highly supercritical bore" def estimate_runup(self, sbsp): """Estimate run-up from SBSP using validated regression R = 19.7 × SBSP − 2.1 [m] Args: sbsp: SBSP value Returns: estimated_runup: estimated run-up height [m] """ runup = 19.7 * sbsp - 2.1 return max(0, runup) def compute_momentum_flux(self, u, H): """Compute depth-integrated momentum flux M = ρ·H·u² Args: u: velocity [m/s] H: depth [m] Returns: M: momentum flux [kg·m/s² per m width] """ rho = 1025 # seawater density return rho * H * u**2 def compute_bore_characteristics(self, u, H, eta): """Compute hydraulic bore characteristics Args: u: velocity [m/s] H: depth [m] eta: wave height [m] Returns: dict with bore characteristics """ Fr = self.compute_froude(u, H) sbsp = self.compute_sbsp(u, H) # Bore height ratio (Boussinesq) if Fr > 1: # Hydraulic jump / bore H2_H1 = 0.5 * (np.sqrt(1 + 8*Fr**2) - 1) else: H2_H1 = 1.0 return { 'froude': float(Fr), 'sbsp': sbsp, 'bore_formation': Fr > 1.0, 'depth_ratio': float(H2_H1), 'bore_height_m': float(H * (H2_H1 - 1)), 'bore_celerity': float(u / (1 - 1/H2_H1)) if Fr > 1 else None } def validate_event(self, u, H, observed_runup): """Validate SBSP against observed run-up""" sbsp_result = self.compute_sbsp(u, H) predicted_runup = self.estimate_runup(sbsp_result['value']) error = abs(predicted_runup - observed_runup) / observed_runup * 100 validation = { 'sbsp': sbsp_result, 'predicted_runup': float(predicted_runup), 'observed_runup': observed_runup, 'error_percent': float(error), 'within_15_percent': error < 15, 'bore': self.compute_bore_characteristics(u, H, observed_runup) } return validation