From 2f9f13fdc8e58dfb438e6594fd35af9b93a76cf7 Mon Sep 17 00:00:00 2001 From: Kevin D Date: Fri, 29 May 2026 19:38:51 -0700 Subject: [PATCH] rf sensor model cleanup --- @rfSensor/RSS.m | 3 +-- @rfSensor/halfAngle.m | 6 +----- @rfSensor/initialize.m | 5 ++++- @rfSensor/plotParameters.m | 7 +++++-- @rfSensor/rfSensor.m | 5 +++-- @rfSensor/transmitterGain.m | 23 ++++++----------------- test/test_miSim.m | 7 ++++--- test/test_rfSensor.m | 32 +++++++++++++++++++------------- 8 files changed, 43 insertions(+), 45 deletions(-) diff --git a/@rfSensor/RSS.m b/@rfSensor/RSS.m index 9d49d35..ff98071 100644 --- a/@rfSensor/RSS.m +++ b/@rfSensor/RSS.m @@ -19,6 +19,5 @@ function value = RSS(obj, d, dx, dy, dz) cos_theta = (st .* (dx .* sa + dy .* ca) - ct .* dz) ./ max(d, eps); cos_theta = max(-1, min(1, cos_theta)); theta = acosd(cos_theta); - gain = 10 .* obj.beamwidthExponent .* log10((1 + cosd(theta)) ./ 2); - value = obj.P_TX_dBm + gain + obj.G_RX_dBi - obj.pathLoss(d); + value = obj.P_TX_dBm + obj.transmitterGain(theta) + obj.G_RX_dBi - obj.pathLoss(d); end diff --git a/@rfSensor/halfAngle.m b/@rfSensor/halfAngle.m index 53fe95d..7d481fd 100644 --- a/@rfSensor/halfAngle.m +++ b/@rfSensor/halfAngle.m @@ -5,12 +5,8 @@ function value = halfAngle(obj) arguments (Output) value (1, 1) double; end - % Sweep angular offset from boresight by evaluating transmitterGain at - % (obj.tilt + dtheta, obj.azimuth). The cosine difference identity guarantees - % the resulting angular offset from boresight equals dtheta exactly, - % independent of the actual pointing direction. dtheta = (0:0.1:179.9)'; - gain = obj.transmitterGain(obj.tilt + dtheta, obj.azimuth * ones(size(dtheta))); + gain = obj.transmitterGain(dtheta); target = gain(1) - 3; idx = find(gain <= target, 1); if isempty(idx) || idx == 1 diff --git a/@rfSensor/initialize.m b/@rfSensor/initialize.m index 156d4f1..813fded 100644 --- a/@rfSensor/initialize.m +++ b/@rfSensor/initialize.m @@ -1,7 +1,8 @@ -function obj = initialize(obj, txPower, bandwidth, centerFreq, rxGain_dBi, beamwidthExponent, tilt, azimuth, lossExponent) +function obj = initialize(obj, txPower, temperature, bandwidth, centerFreq, rxGain_dBi, beamwidthExponent, tilt, azimuth, lossExponent) arguments (Input) obj (1, 1) {mustBeA(obj, "rfSensor")} txPower (1, 1) double; + temperature (1, 1) double; bandwidth (1, 1) double; centerFreq (1, 1) double; rxGain_dBi (1, 1) double; @@ -16,6 +17,7 @@ function obj = initialize(obj, txPower, bandwidth, centerFreq, rxGain_dBi, beamw %% Provided values obj.P_TX = txPower; % Transmit power (W) + obj.T_0 = temperature; % Temperature (K) obj.BW = bandwidth; % Bandwidth (Hz) obj.f_c = centerFreq; % Center frequency (Hz) obj.G_RX_dBi = rxGain_dBi; % Receiving Antenna Gain (dBi) @@ -29,4 +31,5 @@ function obj = initialize(obj, txPower, bandwidth, centerFreq, rxGain_dBi, beamw %% Computed values obj.P_TX_dBm = 10*log10(obj.P_TX/1e-3); % Transmit power in dBm obj.N = obj.k_B * obj.T_0 * obj.BW; % Thermal noise + obj.constantGainTerm_dB = 10 .* log10(2 * (obj.beamwidthExponent + 1)); % used in RSS calculation, only need to compute once end \ No newline at end of file diff --git a/@rfSensor/plotParameters.m b/@rfSensor/plotParameters.m index 5f24a6e..961e58f 100644 --- a/@rfSensor/plotParameters.m +++ b/@rfSensor/plotParameters.m @@ -23,8 +23,11 @@ function f = plotParameters(obj) % For agent at altitude alt, ground target at tilt T has slant distance: D = alt ./ cosd(T); - % Compute RSS for each (d, t, a) triple - rss = obj.RSS(D(:), T(:), A(:)); + % Convert LOS (D, T, A) to displacement vectors for RSS + dx = D .* sind(T) .* sind(A); + dy = D .* sind(T) .* cosd(A); + dz = -D .* cosd(T); % negative: ground targets are below agent + rss = obj.RSS(D(:), dx(:), dy(:), dz(:)); Fslice = reshape(rss, size(D)); % Disc geometry: t=0 (nadir) -> center, t~90 (horizon) -> edge diff --git a/@rfSensor/rfSensor.m b/@rfSensor/rfSensor.m index 8d6fb63..05ced9b 100644 --- a/@rfSensor/rfSensor.m +++ b/@rfSensor/rfSensor.m @@ -14,6 +14,7 @@ classdef rfSensor % Values computed at initialization P_TX_dBm = NaN; % Transmit power (dBm) N = NaN; % Thermal noise + constantGainTerm_dB = NaN; % used in RSS calculation, only need to compute once % Cached state (per timestep) end properties (Access = public) @@ -23,7 +24,7 @@ classdef rfSensor end methods (Access = public) - [obj] = initialize(obj, txPower, bandwidth, centerFreq, rxGain, beamwidthExponent, tilt, azimuth); % initialize sensor, define parameters + [obj] = initialize(obj, txPower, temperature, bandwidth, centerFreq, rxGain, beamwidthExponent, tilt, azimuth); % initialize sensor, define parameters [SINR, SNR, obj, otherSensors] = sensorPerformance(obj, agentPos, targetPos, otherSensorsPos, otherSensors); % determine sensor performance for a given single sensor and target geometry [d, dx, dy, dz] = computePointToPoints(obj, agentPos, targetPos); [value] = halfAngle(obj); % tilt angle (deg) at which sensor performance is halved @@ -34,7 +35,7 @@ classdef rfSensor end methods (Access = private) x = RSS(obj, d, dx, dy, dz); % Received signal strength (function of distance and tilt angle) - G_TX_dB = transmitterGain(obj, t, a); % Antenna gain for a given TX/RX pair + G_TX_dB = transmitterGain(obj, theta); % Antenna gain for a given angle from boresight (degrees) L_FSPL_dB = pathLoss(obj, d); % Free space path loss for a given TX/RX pair end end \ No newline at end of file diff --git a/@rfSensor/transmitterGain.m b/@rfSensor/transmitterGain.m index 6317608..20888c8 100644 --- a/@rfSensor/transmitterGain.m +++ b/@rfSensor/transmitterGain.m @@ -1,23 +1,12 @@ -function value = transmitterGain(obj, t, a) +function value = transmitterGain(obj, theta) arguments (Input) - obj (1, 1) {mustBeA(obj, "rfSensor")}; - t (:, 1) double; % LOS tilt angle - a (:, 1) double; % LOS azimuth angle + obj (1, 1) {mustBeA(obj, "rfSensor")}; + theta (:, 1) double; % angle from boresight (degrees) end arguments (Output) value (:, 1) double end - if ~isequal(size(t), size(a)) - error("t and a must be the same size"); - end - - % Angular offset from boresight via spherical law of cosines - % Convention: t=0° nadir, t=90° horizon; a=0° +y, a=90° +x - cos_theta = sind(obj.tilt) .* sind(t) .* cosd(a - obj.azimuth) + ... - cosd(obj.tilt) .* cosd(t); - cos_theta = max(-1, min(1, cos_theta)); % clamp for numerical safety - theta = acosd(cos_theta); - - % Cardioid family: peak at boresight (theta=0), null opposite (theta=180°) - value = 10 .* obj.beamwidthExponent .* log10((1 + cosd(theta)) ./ 2); + % Cosine pattern: peak at boresight, -inf dB at 90°. + % Clamped so targets behind the antenna (theta > 90°) get -inf rather than NaN. + value = obj.constantGainTerm_dB + 10 .* obj.beamwidthExponent .* log10(max(0, cosd(theta))); end diff --git a/test/test_miSim.m b/test/test_miSim.m index 0a517d3..8600d11 100644 --- a/test/test_miSim.m +++ b/test/test_miSim.m @@ -57,6 +57,7 @@ classdef test_miSim < matlab.unittest.TestCase opticalPartitioningMin = 1e-6; % rfSensor P_TX = 1e-3; % Transmit power (Watts) + T_0 = 300; % Temperature (K) BW = 20e6; % Bandwidth (Hz) f_c = 3e9; % Center frequency (Hz) G_RX_dBi = 3; % Receiving Antenna Gain (dBi) @@ -323,7 +324,7 @@ classdef test_miSim < matlab.unittest.TestCase % Initialize candidate agent sensor model tc.sensor = rfSensor; tilt = 0; azimuth = 0; - tc.sensor = tc.sensor.initialize(tc.P_TX * 1 + rand * 4, tc.BW, tc.f_c, tc.G_RX_dBi, tc.beamwidthExponent + randi(100), tilt, azimuth, tc.lossExponent); + tc.sensor = tc.sensor.initialize(tc.P_TX * 1 + rand * 4, tc.T_0, tc.BW, tc.f_c, tc.G_RX_dBi, tc.beamwidthExponent + randi(100), tilt, azimuth, tc.lossExponent); % Initialize candidate agent newAgent = tc.agents{ii}.initialize(candidatePos, candidateGeometry, tc.sensor, tc.commsRanges(ii), tc.maxIter, tc.initialStepSize, tc.initialMaxAngleStepSize); @@ -659,7 +660,7 @@ classdef test_miSim < matlab.unittest.TestCase geometry1 = geometry1.initialize([tc.domain.center(1:2)-tc.domain.dimensions(1)/4, 3], tc.collisionRanges(1), REGION_TYPE.COLLISION); tc.sensor = rfSensor; - tc.sensor = tc.sensor.initialize(tc.P_TX, tc.BW, tc.f_c, tc.G_RX_dBi, tc.beamwidthExponent, 45, 45, tc.lossExponent); + tc.sensor = tc.sensor.initialize(tc.P_TX, tc.T_0, tc.BW, tc.f_c, tc.G_RX_dBi, tc.beamwidthExponent, 45, 45, tc.lossExponent); % Initialize agents tc.maxIter = 75; @@ -717,7 +718,7 @@ classdef test_miSim < matlab.unittest.TestCase % Initialize agent sensor model tc.sensor = rfSensor; - tc.sensor = tc.sensor.initialize(tc.P_TX, tc.BW, tc.f_c, tc.G_RX_dBi, tc.beamwidthExponent, 0, 0, tc.lossExponent); + tc.sensor = tc.sensor.initialize(tc.P_TX, tc.T_0, tc.BW, tc.f_c, tc.G_RX_dBi, tc.beamwidthExponent, 0, 0, tc.lossExponent); % Initialize agents tc.maxIter = 75; diff --git a/test/test_rfSensor.m b/test/test_rfSensor.m index 1c77e4c..e6a1eae 100644 --- a/test/test_rfSensor.m +++ b/test/test_rfSensor.m @@ -15,28 +15,30 @@ classdef test_rfSensor < matlab.unittest.TestCase function plot_RSS(tc) % Plot sensor performance with no sources of interference P_TX = 1e-3; % Transmit power (Watts) + T_0 = 300; % Temperature (K) BW = 20e6; % Bandwidth (Hz) f_c = 2e9; % Center frequency (Hz) G_RX_dBi = 3; % Receiving Antenna Gain (dBi) beamwidthExponent = 6; lossExponent = 2; - tc.testClass = tc.testClass.initialize(P_TX, BW, f_c, G_RX_dBi, beamwidthExponent, 0, 0, lossExponent); + tc.testClass = tc.testClass.initialize(P_TX, T_0, BW, f_c, G_RX_dBi, beamwidthExponent, 0, 0, lossExponent); tc.testClass.plotParameters(); end function plot_SNR(tc) % Plot sensor performance with no sources of interference P_TX = 1e-3; % Transmit power (Watts) + T_0 = 300; % Temperature (K) BW = 20e6; % Bandwidth (Hz) f_c = 2e9; % Center frequency (Hz) G_RX_dBi = 3; % Receiving Antenna Gain (dBi) beamwidthExponent = 6; lossExponent = 2; - tc.testClass = tc.testClass.initialize(P_TX, BW, f_c, G_RX_dBi, beamwidthExponent, 30, 135, lossExponent); + tc.testClass = tc.testClass.initialize(P_TX, T_0, BW, f_c, G_RX_dBi, beamwidthExponent, 30, 135, lossExponent); - altitude = 30; + altitude = 20; % Boresight azimuth=135° (between +X at 90° and -Y at 180°) → hotspot at +X,-Y. % SNR at (5,-5) should be higher than at (5,+5). @@ -51,13 +53,14 @@ classdef test_rfSensor < matlab.unittest.TestCase function plot_SINR_one_interferer(tc) % Plot sensor performance with no sources of interference P_TX = 1e-3; % Transmit power (Watts) + T_0 = 300; % Temperature (K) BW = 20e6; % Bandwidth (Hz) f_c = 2e9; % Center frequency (Hz) G_RX_dBi = 3; % Receiving Antenna Gain (dBi) beamwidthExponent = 6; lossExponent = 2; - tc.testClass = tc.testClass.initialize(P_TX, BW, f_c, G_RX_dBi, beamwidthExponent, 0, 0, lossExponent); + tc.testClass = tc.testClass.initialize(P_TX, T_0, BW, f_c, G_RX_dBi, beamwidthExponent, 0, 0, lossExponent); altitude = 30; otherSensorsPos = [6, -4, -1]; % relative to main sensor @@ -70,13 +73,14 @@ classdef test_rfSensor < matlab.unittest.TestCase function plot_SINR_heterogenous_interferers(tc) % Plot sensor performance with no sources of interference P_TX = 1e-3; % Transmit power (Watts) + T_0 = 300; % Temperature (K) BW = 20e6; % Bandwidth (Hz) f_c = 2e9; % Center frequency (Hz) G_RX_dBi = 3; % Receiving Antenna Gain (dBi) beamwidthExponent = 6; lossExponent = 2; - tc.testClass = tc.testClass.initialize(P_TX, BW, f_c, G_RX_dBi, beamwidthExponent, 0, 0, lossExponent); + tc.testClass = tc.testClass.initialize(P_TX, T_0, BW, f_c, G_RX_dBi, beamwidthExponent, 0, 0, lossExponent); altitude = 30; otherSensorsPos = [6, -4, -1; -2, 6, 0]; % relative to main sensor @@ -85,13 +89,14 @@ classdef test_rfSensor < matlab.unittest.TestCase otherSensors{2} = rfSensor; % Must use same center frequency and bandwidth for interference sources - otherSensors{1} = otherSensors{1}.initialize(10 * P_TX, BW, f_c, G_RX_dBi, beamwidthExponent, 0, 0, lossExponent); - otherSensors{2} = otherSensors{2}.initialize(10 * P_TX, BW, f_c, G_RX_dBi, beamwidthExponent, 0, 0, lossExponent); + otherSensors{1} = otherSensors{1}.initialize(10 * P_TX, T_0, BW, f_c, G_RX_dBi, beamwidthExponent, 0, 0, lossExponent); + otherSensors{2} = otherSensors{2}.initialize(10 * P_TX, T_0, BW, f_c, G_RX_dBi, beamwidthExponent, 0, 0, lossExponent); tc.testClass.plotPerformance(altitude, otherSensorsPos, otherSensors); end function plot_SINR_heterogenous_interferers_efficiently(tc) P_TX = 1e-3; + T_0 = 300; BW = 20e6; f_c = 2e9; G_RX_dBi = 3; @@ -100,11 +105,11 @@ classdef test_rfSensor < matlab.unittest.TestCase lossExponent = 2; sensor1 = rfSensor; - sensor1 = sensor1.initialize(P_TX, BW, f_c, G_RX_dBi, beamwidthExponent(1), 15, 45, lossExponent); + sensor1 = sensor1.initialize(P_TX, T_0, BW, f_c, G_RX_dBi, beamwidthExponent(1), 15, 45, lossExponent); sensor2 = rfSensor; - sensor2 = sensor2.initialize(P_TX, BW, f_c, G_RX_dBi, beamwidthExponent(2), 10, 150, lossExponent); + sensor2 = sensor2.initialize(P_TX, T_0, BW, f_c, G_RX_dBi, beamwidthExponent(2), 10, 150, lossExponent); sensor3 = rfSensor; - sensor3 = sensor3.initialize(P_TX, BW, f_c, G_RX_dBi, beamwidthExponent(3), 20, 200, lossExponent); + sensor3 = sensor3.initialize(P_TX, T_0, BW, f_c, G_RX_dBi, beamwidthExponent(3), 20, 200, lossExponent); pos1 = [0, 0, altitude]; pos2 = [6, -4, altitude - 1]; @@ -141,6 +146,7 @@ classdef test_rfSensor < matlab.unittest.TestCase end function plot_SINR_heterogenous_interferers_3d(tc) P_TX = 1e-3; + T_0 = 300; BW = 20e6; f_c = 2e9; G_RX_dBi = 3; @@ -149,11 +155,11 @@ classdef test_rfSensor < matlab.unittest.TestCase lossExponent = 2; sensor1 = rfSensor; - sensor1 = sensor1.initialize(P_TX, BW, f_c, G_RX_dBi, beamwidthExponent(1), 15, 45, lossExponent); + sensor1 = sensor1.initialize(P_TX, T_0, BW, f_c, G_RX_dBi, beamwidthExponent(1), 15, 45, lossExponent); sensor2 = rfSensor; - sensor2 = sensor2.initialize(P_TX, BW, f_c, G_RX_dBi, beamwidthExponent(2), 10, 150, lossExponent); + sensor2 = sensor2.initialize(P_TX, T_0, BW, f_c, G_RX_dBi, beamwidthExponent(2), 10, 150, lossExponent); sensor3 = rfSensor; - sensor3 = sensor3.initialize(P_TX, BW, f_c, G_RX_dBi, beamwidthExponent(3), 20, 200, lossExponent); + sensor3 = sensor3.initialize(P_TX, T_0, BW, f_c, G_RX_dBi, beamwidthExponent(3), 20, 200, lossExponent); pos1 = [0, 0, altitude]; pos2 = [6, -4, altitude - 5];