Major refactoring of RFCP backend: - Modular propagation models (8 models) - SharedMemoryManager for terrain data - ProcessPoolExecutor parallel processing - WebSocket progress streaming - Building filtering pipeline (351k → 15k) - 82 unit tests Performance: Standard preset 38s → 5s (7.6x speedup) Known issue: Detailed preset timeout (fix in 3.1.0)
115 lines
3.2 KiB
Python
115 lines
3.2 KiB
Python
"""
|
|
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),
|
|
},
|
|
)
|