""" COST-231 Walfisch-Ikegami model. Valid for: - Frequency: 800-2000 MHz - Distance: 20m-5km - Urban microcell environments Accounts for building heights, street widths, and building separation. Reference: COST 231 Final Report, Chapter 4. """ import math from app.propagation.base import PropagationModel, PropagationInput, PropagationOutput class Cost231WIModel(PropagationModel): @property def name(self) -> str: return "COST-231-WI" @property def frequency_range(self) -> tuple: return (800, 2000) @property def distance_range(self) -> tuple: return (20, 5000) def calculate(self, input: PropagationInput) -> PropagationOutput: f = input.frequency_mhz d = max(input.distance_m / 1000, 0.02) # km hb = max(input.tx_height_m, 4.0) hm = max(input.rx_height_m, 1.0) # Building parameters (defaults for typical urban) h_roof = input.building_height_m or 15.0 # avg building height w = input.street_width_m or 20.0 # street width b = input.building_separation_m or 30.0 # building separation delta_hb = hb - h_roof # TX above rooftop delta_hm = h_roof - hm # rooftop above RX # Free space loss L_fs = 32.45 + 20 * math.log10(d) + 20 * math.log10(f) # LOS case if delta_hb > 0 and d < 0.5: L = L_fs return PropagationOutput( path_loss_db=L, model_name=self.name, is_los=True, breakdown={"free_space": L_fs, "rooftop_diffraction": 0, "multiscreen": 0}, ) # Rooftop-to-street diffraction (L_rts) phi = 90.0 # street orientation angle (worst case) if phi < 35: L_ori = -10 + 0.354 * phi elif phi < 55: L_ori = 2.5 + 0.075 * (phi - 35) else: L_ori = 4.0 - 0.114 * (phi - 55) L_rts = ( -16.9 - 10 * math.log10(w) + 10 * math.log10(f) + 20 * math.log10(delta_hm) + L_ori ) # Multi-screen diffraction (L_msd) if delta_hb > 0: L_bsh = -18 * math.log10(1 + delta_hb) k_a = 54 k_d = 18 else: L_bsh = 0 k_a = 54 - 0.8 * abs(delta_hb) if d >= 0.5: k_a = max(k_a, 54 - 0.8 * abs(delta_hb) * (d / 0.5)) k_d = 18 - 15 * abs(delta_hb) / h_roof k_f = -4 + 0.7 * (f / 925 - 1) # medium city if input.environment == "urban": k_f = -4 + 1.5 * (f / 925 - 1) L_msd = ( L_bsh + k_a + k_d * math.log10(d) + k_f * math.log10(f) - 9 * math.log10(b) ) # Total NLOS loss if L_rts + L_msd > 0: L = L_fs + L_rts + L_msd else: L = L_fs return PropagationOutput( path_loss_db=L, model_name=self.name, is_los=False, breakdown={ "free_space": L_fs, "rooftop_diffraction": max(L_rts, 0), "multiscreen": max(L_msd, 0), }, )