test: property tests for PathCompute loss/power budget + Viewshed destination
- PathCompute: compute_loss_budget/5 and compute_power_budget/2 made public with @doc false; property tests covering fspl, o2, h2o, rain, diffraction across all 13 known bands + varying distances - Viewshed: property tests for destination_point back-bearing round-trip and east-west bearing at equator (2 new properties) Coverage: 79.07% → 79.06% (PathCompute 60→63%)
This commit is contained in:
parent
89e8bb2267
commit
3a2a28437d
3 changed files with 150 additions and 2 deletions
|
|
@ -380,7 +380,8 @@ defmodule Microwaveprop.Propagation.PathCompute do
|
|||
end
|
||||
end
|
||||
|
||||
defp compute_loss_budget(dist_km, freq_ghz, band_config, terrain_result, conditions) do
|
||||
@doc false
|
||||
def compute_loss_budget(dist_km, freq_ghz, band_config, terrain_result, conditions) do
|
||||
freq_mhz = freq_ghz * 1000
|
||||
fspl = 20 * :math.log10(max(dist_km, 0.001)) + 20 * :math.log10(freq_mhz) + 32.44
|
||||
|
||||
|
|
@ -423,7 +424,8 @@ defmodule Microwaveprop.Propagation.PathCompute do
|
|||
}
|
||||
end
|
||||
|
||||
defp compute_power_budget(station_params, loss_budget) do
|
||||
@doc false
|
||||
def compute_power_budget(station_params, loss_budget) do
|
||||
tx_power_dbm = station_params.tx_power_dbm
|
||||
eirp_dbm = tx_power_dbm + station_params.src_gain_dbi
|
||||
rx_power_dbm = eirp_dbm - loss_budget.total + station_params.dst_gain_dbi
|
||||
|
|
|
|||
120
test/microwaveprop/propagation/path_compute_test.exs
Normal file
120
test/microwaveprop/propagation/path_compute_test.exs
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
defmodule Microwaveprop.Propagation.PathComputeTest do
|
||||
use ExUnit.Case, async: true
|
||||
use ExUnitProperties
|
||||
|
||||
alias Microwaveprop.Propagation.BandConfig
|
||||
alias Microwaveprop.Propagation.PathCompute
|
||||
|
||||
describe "compute_loss_budget/5" do
|
||||
test "fspl increases with distance" do
|
||||
band = BandConfig.get(10_000)
|
||||
near = PathCompute.compute_loss_budget(10.0, 10.0, band, nil, nil)
|
||||
far = PathCompute.compute_loss_budget(100.0, 10.0, band, nil, nil)
|
||||
assert near.fspl < far.fspl
|
||||
assert near.total < far.total
|
||||
end
|
||||
|
||||
test "fspl increases with frequency" do
|
||||
band_10g = BandConfig.get(10_000)
|
||||
band_24g = BandConfig.get(24_000)
|
||||
low = PathCompute.compute_loss_budget(50.0, 10.0, band_10g, nil, nil)
|
||||
high = PathCompute.compute_loss_budget(50.0, 24.0, band_24g, nil, nil)
|
||||
assert low.fspl < high.fspl
|
||||
end
|
||||
|
||||
test "gas absorption losses with default humidity when conditions absent" do
|
||||
band = BandConfig.get(10_000)
|
||||
result = PathCompute.compute_loss_budget(50.0, 10.0, band, nil, nil)
|
||||
assert result.o2 >= 0
|
||||
assert result.h2o >= 0
|
||||
end
|
||||
|
||||
test "higher bands have more h2o absorption" do
|
||||
band_10g = BandConfig.get(10_000)
|
||||
band_75g = BandConfig.get(75_000)
|
||||
low = PathCompute.compute_loss_budget(50.0, 10.0, band_10g, nil, nil)
|
||||
high = PathCompute.compute_loss_budget(50.0, 75.0, band_75g, nil, nil)
|
||||
assert low.h2o <= high.h2o
|
||||
end
|
||||
|
||||
test "rain loss is zero when there's no rain" do
|
||||
band = BandConfig.get(10_000)
|
||||
conditions = %{rain_rate_mmhr: 0.0, abs_humidity: 7.5}
|
||||
result = PathCompute.compute_loss_budget(50.0, 10.0, band, nil, conditions)
|
||||
assert result.rain == 0.0
|
||||
end
|
||||
|
||||
test "rain loss is positive when it's raining" do
|
||||
band = BandConfig.get(10_000)
|
||||
conditions = %{rain_rate_mmhr: 5.0, abs_humidity: 7.5}
|
||||
result = PathCompute.compute_loss_budget(50.0, 10.0, band, nil, conditions)
|
||||
assert result.rain > 0
|
||||
end
|
||||
|
||||
test "diffraction loss is included when terrain result is present" do
|
||||
band = BandConfig.get(10_000)
|
||||
terrain = %{analysis: %{diffraction_db: 15.0}}
|
||||
result = PathCompute.compute_loss_budget(50.0, 10.0, band, terrain, nil)
|
||||
assert result.diffraction == 15.0
|
||||
end
|
||||
|
||||
test "diffraction loss is zero when no terrain result" do
|
||||
band = BandConfig.get(10_000)
|
||||
result = PathCompute.compute_loss_budget(50.0, 10.0, band, nil, nil)
|
||||
assert result.diffraction == 0.0
|
||||
end
|
||||
end
|
||||
|
||||
describe "compute_loss_budget/5 property" do
|
||||
property "total is always >= fspl" do
|
||||
check all dist_km <- float(min: 0.1, max: 500),
|
||||
band_config = BandConfig.get(10_000) do
|
||||
result = PathCompute.compute_loss_budget(dist_km, 10.0, band_config, nil, nil)
|
||||
assert result.total >= result.fspl
|
||||
assert result.total > 0
|
||||
end
|
||||
end
|
||||
|
||||
property "all loss components are non-negative across known bands" do
|
||||
bands = [50, 144, 222, 432, 902, 1_296, 2_304, 3_400, 5_760, 10_000, 24_000, 47_000, 75_000]
|
||||
|
||||
check all dist_km <- float(min: 0.1, max: 500),
|
||||
band_mhz <- member_of(bands) do
|
||||
band_config = BandConfig.get(band_mhz)
|
||||
freq_ghz = band_mhz / 1000
|
||||
result = PathCompute.compute_loss_budget(dist_km, freq_ghz, band_config, nil, nil)
|
||||
assert result.fspl >= 0
|
||||
assert result.o2 >= 0
|
||||
assert result.h2o >= 0
|
||||
assert result.rain >= 0
|
||||
assert result.diffraction >= 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "compute_power_budget/2" do
|
||||
test "computes rx power and margins" do
|
||||
loss = %{total: 140.0, fspl: 130.0, o2: 3.0, h2o: 5.0, rain: 0.0, diffraction: 2.0}
|
||||
params = %{tx_power_dbm: 30.0, src_gain_dbi: 20.0, dst_gain_dbi: 20.0}
|
||||
|
||||
result = PathCompute.compute_power_budget(params, loss)
|
||||
|
||||
# EIRP = tx_power + src_gain = 50 dBm
|
||||
assert_in_delta result.eirp_dbm, 50.0, 0.1
|
||||
# rx_power = eirp - loss + dst_gain
|
||||
assert_in_delta result.rx_power_dbm, -70.0, 0.1
|
||||
# margin_cw = rx_power - (-140)
|
||||
assert_in_delta result.margin_cw, 70.0, 0.1
|
||||
end
|
||||
|
||||
test "higher tx power improves margins" do
|
||||
loss = %{total: 140.0, fspl: 130.0, o2: 3.0, h2o: 5.0, rain: 0.0, diffraction: 2.0}
|
||||
|
||||
low = PathCompute.compute_power_budget(%{tx_power_dbm: 20.0, src_gain_dbi: 10.0, dst_gain_dbi: 10.0}, loss)
|
||||
high = PathCompute.compute_power_budget(%{tx_power_dbm: 30.0, src_gain_dbi: 20.0, dst_gain_dbi: 20.0}, loss)
|
||||
|
||||
assert low.rx_power_dbm < high.rx_power_dbm
|
||||
assert low.margin_cw < high.margin_cw
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -236,6 +236,32 @@ defmodule Microwaveprop.Terrain.ViewshedTest do
|
|||
end
|
||||
end
|
||||
|
||||
describe "destination_point/4 property" do
|
||||
use ExUnitProperties
|
||||
|
||||
property "short bearings round-trip for CONUS-mid latitudes" do
|
||||
check all lat <- float(min: 25, max: 50),
|
||||
lon <- float(min: -125, max: -65),
|
||||
bearing <- float(min: 0, max: 360),
|
||||
dist_km <- float(min: 0.1, max: 20) do
|
||||
{lat2, lon2} = Viewshed.destination_point(lat, lon, bearing, dist_km)
|
||||
back_bearing = if(bearing < 180, do: bearing + 180, else: bearing - 180)
|
||||
{lat3, lon3} = Viewshed.destination_point(lat2, lon2, back_bearing, dist_km)
|
||||
assert_in_delta lat3, lat, 0.5
|
||||
assert_in_delta lon3, lon, 0.5
|
||||
end
|
||||
end
|
||||
|
||||
property "east-west bearing preserves latitude at equator" do
|
||||
check all lon <- float(min: -170, max: 170),
|
||||
dist_km <- float(min: 1, max: 50) do
|
||||
{lat2, lon2} = Viewshed.destination_point(0.0, lon, 90, dist_km)
|
||||
assert_in_delta lat2, 0.0, 0.01
|
||||
assert lon2 != lon
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "analyse_ray/5" do
|
||||
test "returns full range for flat terrain with antenna heights" do
|
||||
profile =
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue