import math class AtmosphericService: """ITU-R P.676 atmospheric absorption model""" # Simplified model for frequencies < 50 GHz # Standard atmosphere: T=15C, P=1013 hPa, humidity=50% def calculate_atmospheric_loss( self, frequency_mhz: float, distance_km: float, temperature_c: float = 15.0, humidity_percent: float = 50.0, altitude_m: float = 0.0, ) -> float: """ Calculate atmospheric absorption loss Args: frequency_mhz: Frequency in MHz distance_km: Path length in km temperature_c: Temperature in Celsius humidity_percent: Relative humidity (0-100) altitude_m: Altitude above sea level Returns: Loss in dB """ freq_ghz = frequency_mhz / 1000 # Below 1 GHz - negligible if freq_ghz < 1.0: return 0.0 # Calculate specific attenuation (dB/km) gamma = self._specific_attenuation(freq_ghz, temperature_c, humidity_percent) # Altitude correction (less atmosphere at higher altitudes) altitude_factor = math.exp(-altitude_m / 8500) # Scale height ~8.5km loss = gamma * distance_km * altitude_factor return min(loss, 20.0) # Cap for reasonable distances def _specific_attenuation( self, freq_ghz: float, temperature_c: float, humidity_percent: float, ) -> float: """ Calculate specific attenuation in dB/km Simplified ITU-R P.676 model """ # Water vapor density (g/m3) - simplified # Saturation vapor pressure (hPa) es = 6.1121 * math.exp( (18.678 - temperature_c / 234.5) * (temperature_c / (257.14 + temperature_c)) ) rho = (humidity_percent / 100) * es * 216.7 / (273.15 + temperature_c) # Oxygen absorption (dominant at 60 GHz, minor below 10 GHz) if freq_ghz < 10: gamma_o = 0.001 * freq_ghz ** 2 # Very small elif freq_ghz < 57: gamma_o = 0.001 * (freq_ghz / 10) ** 2.5 else: # Near 60 GHz resonance gamma_o = 15.0 # Peak absorption # Water vapor absorption (peaks at 22 GHz and 183 GHz) if freq_ghz < 10: gamma_w = 0.0001 * rho * freq_ghz ** 2 elif freq_ghz < 50: gamma_w = 0.001 * rho * (freq_ghz / 22) ** 2 else: gamma_w = 0.01 * rho return gamma_o + gamma_w @staticmethod def get_weather_description(loss_db: float) -> str: """Describe atmospheric conditions based on loss""" if loss_db < 0.1: return "clear" elif loss_db < 0.5: return "normal" elif loss_db < 2.0: return "humid" else: return "foggy" atmospheric_service = AtmosphericService()