72 lines
2.2 KiB
Python
72 lines
2.2 KiB
Python
from __future__ import annotations
|
|
|
|
from math import log10, sqrt
|
|
|
|
from app.core.fresnel import wavelength
|
|
from app.core.surface import SurfaceProfile
|
|
|
|
|
|
def knife_edge_loss(v: float) -> float:
|
|
if v <= -0.78:
|
|
return 0.0
|
|
return 6.9 + 20 * log10(sqrt((v - 0.1) ** 2 + 1) + v - 0.1)
|
|
|
|
|
|
def knife_edge_v(h: float, d1: float, d2: float, freq_hz: float) -> float:
|
|
if d1 <= 0 or d2 <= 0:
|
|
return float("-inf")
|
|
lmbda = wavelength(freq_hz)
|
|
return h * sqrt(2 * (d1 + d2) / (lmbda * d1 * d2))
|
|
|
|
|
|
def deygout(
|
|
profile: SurfaceProfile,
|
|
tx_height_agl: float,
|
|
rx_height_agl: float,
|
|
freq_hz: float,
|
|
) -> float:
|
|
"""Recursive Deygout diffraction loss using the dominant edge and subprofiles."""
|
|
if len(profile.samples) < 3:
|
|
return 0.0
|
|
|
|
endpoint_heights = {
|
|
0: profile.samples[0].ground_m + tx_height_agl,
|
|
len(profile.samples) - 1: profile.samples[-1].ground_m + rx_height_agl,
|
|
}
|
|
|
|
def sample_height(index: int) -> float:
|
|
return endpoint_heights.get(index, profile.samples[index].surface_m)
|
|
|
|
def solve(left: int, right: int) -> float:
|
|
if right - left < 2:
|
|
return 0.0
|
|
|
|
left_sample = profile.samples[left]
|
|
right_sample = profile.samples[right]
|
|
span_m = right_sample.distance_m - left_sample.distance_m
|
|
left_height = sample_height(left)
|
|
right_height = sample_height(right)
|
|
max_v = float("-inf")
|
|
max_index: int | None = None
|
|
|
|
for index in range(left + 1, right):
|
|
sample = profile.samples[index]
|
|
d1 = sample.distance_m - left_sample.distance_m
|
|
d2 = right_sample.distance_m - sample.distance_m
|
|
path_height = left_height + (right_height - left_height) * (d1 / span_m)
|
|
h = sample.surface_m - path_height
|
|
v = knife_edge_v(h, d1, d2, freq_hz)
|
|
if v > max_v:
|
|
max_v = v
|
|
max_index = index
|
|
|
|
if max_index is None:
|
|
return 0.0
|
|
|
|
main_loss = knife_edge_loss(max_v)
|
|
if main_loss == 0.0:
|
|
return 0.0
|
|
return main_loss + solve(left, max_index) + solve(max_index, right)
|
|
|
|
return solve(0, len(profile.samples) - 1)
|