From dd0861d11c6da11f25ef6ea5fd643c7af72bcf56 Mon Sep 17 00:00:00 2001 From: Kevin D Date: Sun, 19 Apr 2026 12:25:05 -0700 Subject: [PATCH 01/32] began developing new sensor model --- @rfSensor/RSS.m | 16 +++++++ @rfSensor/antennaGain.m | 12 +++++ @rfSensor/initialize.m | 9 ++++ @rfSensor/pathLoss.m | 12 +++++ @rfSensor/plotParameters.m | 47 +++++++++++++++++++ @rfSensor/rfSensor.m | 23 +++++++++ @rfSensor/sensorPerformance.m | 36 ++++++++++++++ @sigmoidSensor/sigmoidSensor.m | 10 ++-- .../BsVG5tuGBaruV5jvy4e3yG1jm4Qd.xml | 6 +++ .../BsVG5tuGBaruV5jvy4e3yG1jm4Qp.xml | 2 + .../0TUOPB3gylGuEK1Q5NsFsa57FBQd.xml | 6 +++ .../0TUOPB3gylGuEK1Q5NsFsa57FBQp.xml | 2 + .../9BCm86xMEE43ruM6njCMPywT3mgd.xml | 6 +++ .../9BCm86xMEE43ruM6njCMPywT3mgp.xml | 2 + .../DKU4t7TpyzULRGxIyA4AFql5gKQd.xml | 6 +++ .../DKU4t7TpyzULRGxIyA4AFql5gKQp.xml | 2 + .../Waxi82DIfDNaoB9qVMSu-XeU4h8d.xml | 6 +++ .../Waxi82DIfDNaoB9qVMSu-XeU4h8p.xml | 2 + .../bflJ_m_JEPly6uLay0BQMV_1uAUd.xml | 6 +++ .../bflJ_m_JEPly6uLay0BQMV_1uAUp.xml | 2 + .../jvRgOU4zI43Zz6soLWzgDAjnvmod.xml | 2 + .../jvRgOU4zI43Zz6soLWzgDAjnvmop.xml | 2 + .../ugpKFhW2eBP_pSSkV0NyOiJaMEod.xml | 6 +++ .../ugpKFhW2eBP_pSSkV0NyOiJaMEop.xml | 2 + .../vKw7EZP5Lkw7wHPSRNjXYJUWEOId.xml | 6 +++ .../vKw7EZP5Lkw7wHPSRNjXYJUWEOIp.xml | 2 + .../MoO8eFA7MNcwhsnIRzm_IDZfmpgd.xml | 2 + .../MoO8eFA7MNcwhsnIRzm_IDZfmpgp.xml | 2 + test/test_rfSensor.m | 25 ++++++++++ 29 files changed, 257 insertions(+), 5 deletions(-) create mode 100644 @rfSensor/RSS.m create mode 100644 @rfSensor/antennaGain.m create mode 100644 @rfSensor/initialize.m create mode 100644 @rfSensor/pathLoss.m create mode 100644 @rfSensor/plotParameters.m create mode 100644 @rfSensor/rfSensor.m create mode 100644 @rfSensor/sensorPerformance.m create mode 100644 resources/project/FI0gxbH-PhwjE_riDQGHPyYMHks/BsVG5tuGBaruV5jvy4e3yG1jm4Qd.xml create mode 100644 resources/project/FI0gxbH-PhwjE_riDQGHPyYMHks/BsVG5tuGBaruV5jvy4e3yG1jm4Qp.xml create mode 100644 resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/0TUOPB3gylGuEK1Q5NsFsa57FBQd.xml create mode 100644 resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/0TUOPB3gylGuEK1Q5NsFsa57FBQp.xml create mode 100644 resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/9BCm86xMEE43ruM6njCMPywT3mgd.xml create mode 100644 resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/9BCm86xMEE43ruM6njCMPywT3mgp.xml create mode 100644 resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/DKU4t7TpyzULRGxIyA4AFql5gKQd.xml create mode 100644 resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/DKU4t7TpyzULRGxIyA4AFql5gKQp.xml create mode 100644 resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/Waxi82DIfDNaoB9qVMSu-XeU4h8d.xml create mode 100644 resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/Waxi82DIfDNaoB9qVMSu-XeU4h8p.xml create mode 100644 resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/bflJ_m_JEPly6uLay0BQMV_1uAUd.xml create mode 100644 resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/bflJ_m_JEPly6uLay0BQMV_1uAUp.xml create mode 100644 resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/jvRgOU4zI43Zz6soLWzgDAjnvmod.xml create mode 100644 resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/jvRgOU4zI43Zz6soLWzgDAjnvmop.xml create mode 100644 resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/ugpKFhW2eBP_pSSkV0NyOiJaMEod.xml create mode 100644 resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/ugpKFhW2eBP_pSSkV0NyOiJaMEop.xml create mode 100644 resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/vKw7EZP5Lkw7wHPSRNjXYJUWEOId.xml create mode 100644 resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/vKw7EZP5Lkw7wHPSRNjXYJUWEOIp.xml create mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/MoO8eFA7MNcwhsnIRzm_IDZfmpgd.xml create mode 100644 resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/MoO8eFA7MNcwhsnIRzm_IDZfmpgp.xml create mode 100644 test/test_rfSensor.m diff --git a/@rfSensor/RSS.m b/@rfSensor/RSS.m new file mode 100644 index 0000000..db0fcaf --- /dev/null +++ b/@rfSensor/RSS.m @@ -0,0 +1,16 @@ +function value = RSS(obj, d, t) + arguments (Input) + obj (1, 1) {mustBeA(obj, "rfSensor")}; + d (:, 1) double; % distance from agent to target + t (:, 1) double; % LOS tilt angle + end + arguments (Output) + value (:, 1) double + end + + rho_dBm = 10*log10(obj.P_TX/1e-3); + + % RSS = TX Power + Antenna Gain - Path Loss + value = rho_dBm + obj.antennaGain(t) - obj.pathLoss(d); + +end \ No newline at end of file diff --git a/@rfSensor/antennaGain.m b/@rfSensor/antennaGain.m new file mode 100644 index 0000000..7fb9f3a --- /dev/null +++ b/@rfSensor/antennaGain.m @@ -0,0 +1,12 @@ +function value = antennaGain(obj, t) + arguments (Input) + obj (1, 1) {mustBeA(obj, "rfSensor")}; + t (:, 1) double; % LOS tilt angle + end + arguments (Output) + value (:, 1) double + end + + % TODO + value = 10*log10(1); +end diff --git a/@rfSensor/initialize.m b/@rfSensor/initialize.m new file mode 100644 index 0000000..dececa7 --- /dev/null +++ b/@rfSensor/initialize.m @@ -0,0 +1,9 @@ +function obj = initialize(obj) + arguments (Input) + obj (1, 1) {mustBeA(obj, "rfSensor")} + end + arguments (Output) + obj (1, 1) {mustBeA(obj, "rfSensor")} + end + +end \ No newline at end of file diff --git a/@rfSensor/pathLoss.m b/@rfSensor/pathLoss.m new file mode 100644 index 0000000..69f51d9 --- /dev/null +++ b/@rfSensor/pathLoss.m @@ -0,0 +1,12 @@ +function L_FSPL_dB = pathLoss(obj, d) + arguments (Input) + obj (1, 1) {mustBeA(obj, "rfSensor")}; + d (:, 1) double; % distance from TX to RX + end + arguments (Output) + L_FSPL_dB (:, 1) double + end + + L_FSPL_dB = 20*log10(d) + 20*log10(obj.f_c) + 20*log10((4*pi)/obj.c); + +end diff --git a/@rfSensor/plotParameters.m b/@rfSensor/plotParameters.m new file mode 100644 index 0000000..4fa481b --- /dev/null +++ b/@rfSensor/plotParameters.m @@ -0,0 +1,47 @@ +function f = plotParameters(obj) + arguments (Input) + obj (1, 1) {mustBeA(obj, "rfSensor")}; + end + arguments (Output) + f (1, 1) {mustBeA(f, "matlab.ui.Figure")}; + end + + % Distance and tilt sample points + d = [0.01, 0.1, 0.25, 0.5, 0.75, 1:1:100]; + t = zeros(size(d)); + + % Sample RSS function by distances, tilts + r_x = obj.RSS(d', t'); + + % Sample SINR (SNR) function by distances, tilts + % using SINR method with no other transmitters defined is equivalent to SNR + s_x = NaN(size(r_x)); + for ii = 1:size(s_x, 1) + s_x(ii) = obj.sensorPerformance([0, 0, d(ii)], zeros(1, 3)); % don't define other sensors + end + + % Plot resultant sigmoid curves + f = figure; + tiledlayout(f, 2, 1, "TileSpacing", "tight", "Padding", "compact"); + + % RSS/Distance with 0 tilt + nexttile(1, [1, 1]); + grid("on"); + title("RSS vs Distance"); + xlabel("Distance (m)"); + ylabel("RSS (dBm)"); + hold("on"); + plot(d, r_x, "LineWidth", 2); + hold("off"); + % ylim([0, 1]); + + % SNR/Distance with 0 tilt + nexttile(2, [1, 1]); + grid("on"); + title("SNR vs Distance"); + xlabel("Distance (m)"); + ylabel("SNR (dB)"); + hold("on"); + plot(d, s_x, "LineWidth", 2); + hold("off"); +end \ No newline at end of file diff --git a/@rfSensor/rfSensor.m b/@rfSensor/rfSensor.m new file mode 100644 index 0000000..4f9c454 --- /dev/null +++ b/@rfSensor/rfSensor.m @@ -0,0 +1,23 @@ +classdef rfSensor + properties (SetAccess = private, GetAccess = public) + % Physical parameters + c = 3e8; % Speed of light (m/s) + k_B = 1.38e-23 % Boltzmann constant (W/Hz/K) for thermal noise model + T_0 = 300; % Ambient temperature (Kelvin) for thermal noise model + % Sensor parameters + P_TX = 10e-3; % Transmit power (Watts) + BW = 1e6; % Bandwidth (Hz) + f_c = 2e9; % Center frequency (Hz) + end + + methods (Access = public) + [obj] = initialize(obj, alphaDist, betaDist, alphaTilt, betaTilt); % TODO initialize sensor, define parameters + [SINR] = sensorPerformance(obj, agentPos, agentPan, agentTilt, targetPos); % determine sensor performance for a given single sensor and target geometry + [f] = plotParameters(obj); % debug, plot sensor response as a function of distance and tilt angle + end + methods (Access = private) + x = RSS(obj, d, t); % Received signal strength (function of distance and tilt angle) + G_TX_dB = antennaGain(obj, agentPos, targetPos); % TODO Antenna gain for a given TX/RX pair + L_FSPL_dB = pathLoss(obj, agentPos, targetPos); % Free space path loss for a given TX/RX pair + end +end \ No newline at end of file diff --git a/@rfSensor/sensorPerformance.m b/@rfSensor/sensorPerformance.m new file mode 100644 index 0000000..c88d7d5 --- /dev/null +++ b/@rfSensor/sensorPerformance.m @@ -0,0 +1,36 @@ +function SINR = sensorPerformance(obj, agentPos, targetPos, otherSensors, otherSensorsPos) + arguments (Input) + obj (1, 1) {mustBeA(obj, "rfSensor")}; + agentPos (1, 3) double; + targetPos (1, 3) double; + otherSensors (:, 1) cell = {}; + otherSensorsPos (:, 3) double = NaN(0, 3); + end + arguments (Output) + SINR (:, 1) double; + end + assert(size(otherSensors, 1) == size(otherSensorsPos, 1), "Mismatch in length of other sensors (%d) and their positions (%d)", size(otherSensors, 1), size(otherSensorsPos, 1)); + + d = vecnorm(agentPos - targetPos, 2, 2); % distance from sensor to target + d_other = NaN(size(otherSensors)); + + x = vecnorm(agentPos(1:2) - targetPos(1, 1:2), 2, 2); % distance from sensor nadir to target nadir (i.e. distance ignoring height difference) + x_other = NaN(size(otherSensors)); + + t = (180 - atan2d(x, targetPos(1, 3) - agentPos(3))); % degrees + t_other = NaN(size(otherSensors)); + + % Performance is measured as SINR for this sensor + S = 10^(0.1 * obj.RSS(d, t)); % Signal + N = obj.k_B * obj.T_0 * obj.BW; % Thermal noise + I = 0; % Interference from other agents + for ii = 1:size(otherSensors, 1) + d_other(ii, 1) = vecnorm(otherSensorsPos(ii, 1:3) - targetPos, 2, 2); + + x_other(ii, 1) = vecnorm(otherSensorsPos(ii, 3) - targetPos(1, 1:2), 2, 2); + t_other(ii, 1) = (180 - atan2d(x_other(ii, 1), targetPos(1, 3) - otherSensorsPos(ii, 3))); + + I = I + 10 ^ (0.1 * otherSensors.RSS(otherSensors{ii}, d_other(ii), t_other(ii))); + end + SINR = 10*log10(S/(I + N)); +end \ No newline at end of file diff --git a/@sigmoidSensor/sigmoidSensor.m b/@sigmoidSensor/sigmoidSensor.m index c1a8e28..e25bd79 100644 --- a/@sigmoidSensor/sigmoidSensor.m +++ b/@sigmoidSensor/sigmoidSensor.m @@ -8,12 +8,12 @@ classdef sigmoidSensor end methods (Access = public) - [obj] = initialize(obj, alphaDist, betaDist, alphaTilt, betaTilt); - [value] = sensorPerformance(obj, agentPos, agentPan, agentTilt, targetPos); - [f] = plotParameters(obj); + [obj] = initialize(obj, alphaDist, betaDist, alphaTilt, betaTilt); % initialize sensor, define parameters + [value] = sensorPerformance(obj, agentPos, agentPan, agentTilt, targetPos); % determine sensor performance for a given single sensor and target geometry + [f] = plotParameters(obj); % debug, plot sensor response as a function of distance and tilt angle end methods (Access = private) - x = distanceMembership(obj, d); - x = tiltMembership(obj, t); + x = distanceMembership(obj, d); % used in computing distance factor of sensor performance + x = tiltMembership(obj, t); % used in computing tilt factor of sensor performance end end \ No newline at end of file diff --git a/resources/project/FI0gxbH-PhwjE_riDQGHPyYMHks/BsVG5tuGBaruV5jvy4e3yG1jm4Qd.xml b/resources/project/FI0gxbH-PhwjE_riDQGHPyYMHks/BsVG5tuGBaruV5jvy4e3yG1jm4Qd.xml new file mode 100644 index 0000000..378b613 --- /dev/null +++ b/resources/project/FI0gxbH-PhwjE_riDQGHPyYMHks/BsVG5tuGBaruV5jvy4e3yG1jm4Qd.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/resources/project/FI0gxbH-PhwjE_riDQGHPyYMHks/BsVG5tuGBaruV5jvy4e3yG1jm4Qp.xml b/resources/project/FI0gxbH-PhwjE_riDQGHPyYMHks/BsVG5tuGBaruV5jvy4e3yG1jm4Qp.xml new file mode 100644 index 0000000..180f4cc --- /dev/null +++ b/resources/project/FI0gxbH-PhwjE_riDQGHPyYMHks/BsVG5tuGBaruV5jvy4e3yG1jm4Qp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/0TUOPB3gylGuEK1Q5NsFsa57FBQd.xml b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/0TUOPB3gylGuEK1Q5NsFsa57FBQd.xml new file mode 100644 index 0000000..99772b4 --- /dev/null +++ b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/0TUOPB3gylGuEK1Q5NsFsa57FBQd.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/0TUOPB3gylGuEK1Q5NsFsa57FBQp.xml b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/0TUOPB3gylGuEK1Q5NsFsa57FBQp.xml new file mode 100644 index 0000000..4401f9c --- /dev/null +++ b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/0TUOPB3gylGuEK1Q5NsFsa57FBQp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/9BCm86xMEE43ruM6njCMPywT3mgd.xml b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/9BCm86xMEE43ruM6njCMPywT3mgd.xml new file mode 100644 index 0000000..99772b4 --- /dev/null +++ b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/9BCm86xMEE43ruM6njCMPywT3mgd.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/9BCm86xMEE43ruM6njCMPywT3mgp.xml b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/9BCm86xMEE43ruM6njCMPywT3mgp.xml new file mode 100644 index 0000000..a8cb24d --- /dev/null +++ b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/9BCm86xMEE43ruM6njCMPywT3mgp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/DKU4t7TpyzULRGxIyA4AFql5gKQd.xml b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/DKU4t7TpyzULRGxIyA4AFql5gKQd.xml new file mode 100644 index 0000000..99772b4 --- /dev/null +++ b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/DKU4t7TpyzULRGxIyA4AFql5gKQd.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/DKU4t7TpyzULRGxIyA4AFql5gKQp.xml b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/DKU4t7TpyzULRGxIyA4AFql5gKQp.xml new file mode 100644 index 0000000..a6d7c3c --- /dev/null +++ b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/DKU4t7TpyzULRGxIyA4AFql5gKQp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/Waxi82DIfDNaoB9qVMSu-XeU4h8d.xml b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/Waxi82DIfDNaoB9qVMSu-XeU4h8d.xml new file mode 100644 index 0000000..99772b4 --- /dev/null +++ b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/Waxi82DIfDNaoB9qVMSu-XeU4h8d.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/Waxi82DIfDNaoB9qVMSu-XeU4h8p.xml b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/Waxi82DIfDNaoB9qVMSu-XeU4h8p.xml new file mode 100644 index 0000000..919c37e --- /dev/null +++ b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/Waxi82DIfDNaoB9qVMSu-XeU4h8p.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/bflJ_m_JEPly6uLay0BQMV_1uAUd.xml b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/bflJ_m_JEPly6uLay0BQMV_1uAUd.xml new file mode 100644 index 0000000..99772b4 --- /dev/null +++ b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/bflJ_m_JEPly6uLay0BQMV_1uAUd.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/bflJ_m_JEPly6uLay0BQMV_1uAUp.xml b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/bflJ_m_JEPly6uLay0BQMV_1uAUp.xml new file mode 100644 index 0000000..844d632 --- /dev/null +++ b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/bflJ_m_JEPly6uLay0BQMV_1uAUp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/jvRgOU4zI43Zz6soLWzgDAjnvmod.xml b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/jvRgOU4zI43Zz6soLWzgDAjnvmod.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/jvRgOU4zI43Zz6soLWzgDAjnvmod.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/jvRgOU4zI43Zz6soLWzgDAjnvmop.xml b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/jvRgOU4zI43Zz6soLWzgDAjnvmop.xml new file mode 100644 index 0000000..01cb34e --- /dev/null +++ b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/jvRgOU4zI43Zz6soLWzgDAjnvmop.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/ugpKFhW2eBP_pSSkV0NyOiJaMEod.xml b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/ugpKFhW2eBP_pSSkV0NyOiJaMEod.xml new file mode 100644 index 0000000..99772b4 --- /dev/null +++ b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/ugpKFhW2eBP_pSSkV0NyOiJaMEod.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/ugpKFhW2eBP_pSSkV0NyOiJaMEop.xml b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/ugpKFhW2eBP_pSSkV0NyOiJaMEop.xml new file mode 100644 index 0000000..ede8992 --- /dev/null +++ b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/ugpKFhW2eBP_pSSkV0NyOiJaMEop.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/vKw7EZP5Lkw7wHPSRNjXYJUWEOId.xml b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/vKw7EZP5Lkw7wHPSRNjXYJUWEOId.xml new file mode 100644 index 0000000..99772b4 --- /dev/null +++ b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/vKw7EZP5Lkw7wHPSRNjXYJUWEOId.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/vKw7EZP5Lkw7wHPSRNjXYJUWEOIp.xml b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/vKw7EZP5Lkw7wHPSRNjXYJUWEOIp.xml new file mode 100644 index 0000000..cde7183 --- /dev/null +++ b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/vKw7EZP5Lkw7wHPSRNjXYJUWEOIp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/MoO8eFA7MNcwhsnIRzm_IDZfmpgd.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/MoO8eFA7MNcwhsnIRzm_IDZfmpgd.xml new file mode 100644 index 0000000..4356a6a --- /dev/null +++ b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/MoO8eFA7MNcwhsnIRzm_IDZfmpgd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/MoO8eFA7MNcwhsnIRzm_IDZfmpgp.xml b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/MoO8eFA7MNcwhsnIRzm_IDZfmpgp.xml new file mode 100644 index 0000000..eea8c49 --- /dev/null +++ b/resources/project/qaw0eS1zuuY1ar9TdPn1GMfrjbQ/MoO8eFA7MNcwhsnIRzm_IDZfmpgp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/test/test_rfSensor.m b/test/test_rfSensor.m new file mode 100644 index 0000000..61bcd79 --- /dev/null +++ b/test/test_rfSensor.m @@ -0,0 +1,25 @@ +classdef test_rfSensor < matlab.unittest.TestCase + properties (Access = private) + % System under test + testClass = sigmoidSensor; + end + + methods (TestMethodSetup) + function tc = setup(tc) + % Reinitialize sensor with random parameters + tc.testClass = rfSensor; + % TODO + tc.testClass = tc.testClass.initialize(); + end + end + + methods (Test) + % Test methods + + function test_SINR(tc) + tc.testClass.plotParameters(); + % [SINR] = tc.testClass.sensorPerformance(obj, agentPos, agentPan, agentTilt, targetPos); + end + end + +end \ No newline at end of file From 275123d0fc31712831574c7ce1efc53304add2a5 Mon Sep 17 00:00:00 2001 From: Kevin D Date: Tue, 21 Apr 2026 09:14:38 -0700 Subject: [PATCH 02/32] cleanup before refactoring --- @rfSensor/antennaGain.m | 3 ++- @rfSensor/initialize.m | 9 ++++++++- @rfSensor/plotParameters.m | 1 + @rfSensor/rfSensor.m | 8 ++++---- test/test_rfSensor.m | 22 ++++++++++++++++------ 5 files changed, 31 insertions(+), 12 deletions(-) diff --git a/@rfSensor/antennaGain.m b/@rfSensor/antennaGain.m index 7fb9f3a..4fdd4e7 100644 --- a/@rfSensor/antennaGain.m +++ b/@rfSensor/antennaGain.m @@ -8,5 +8,6 @@ function value = antennaGain(obj, t) end % TODO - value = 10*log10(1); + % Temporary logic to make nadir-pointing most effective + value = 10*log10(cosd(t)); end diff --git a/@rfSensor/initialize.m b/@rfSensor/initialize.m index dececa7..20c3f87 100644 --- a/@rfSensor/initialize.m +++ b/@rfSensor/initialize.m @@ -1,9 +1,16 @@ -function obj = initialize(obj) +function obj = initialize(obj, txPower, bandwidth, centerFreq) arguments (Input) obj (1, 1) {mustBeA(obj, "rfSensor")} + txPower (1, 1) double; + bandwidth (1, 1) double; + centerFreq (1, 1) double; end arguments (Output) obj (1, 1) {mustBeA(obj, "rfSensor")} end + + obj.P_TX = txPower; + obj.BW = bandwidth; + obj.f_c = centerFreq; end \ No newline at end of file diff --git a/@rfSensor/plotParameters.m b/@rfSensor/plotParameters.m index 4fa481b..1a39138 100644 --- a/@rfSensor/plotParameters.m +++ b/@rfSensor/plotParameters.m @@ -9,6 +9,7 @@ function f = plotParameters(obj) % Distance and tilt sample points d = [0.01, 0.1, 0.25, 0.5, 0.75, 1:1:100]; t = zeros(size(d)); + t = -90:1:90; % Sample RSS function by distances, tilts r_x = obj.RSS(d', t'); diff --git a/@rfSensor/rfSensor.m b/@rfSensor/rfSensor.m index 4f9c454..9759fd0 100644 --- a/@rfSensor/rfSensor.m +++ b/@rfSensor/rfSensor.m @@ -5,13 +5,13 @@ classdef rfSensor k_B = 1.38e-23 % Boltzmann constant (W/Hz/K) for thermal noise model T_0 = 300; % Ambient temperature (Kelvin) for thermal noise model % Sensor parameters - P_TX = 10e-3; % Transmit power (Watts) - BW = 1e6; % Bandwidth (Hz) - f_c = 2e9; % Center frequency (Hz) + P_TX = NaN; % Transmit power (Watts) + BW = NaN; % Bandwidth (Hz) + f_c = NaN; % Center frequency (Hz) end methods (Access = public) - [obj] = initialize(obj, alphaDist, betaDist, alphaTilt, betaTilt); % TODO initialize sensor, define parameters + [obj] = initialize(obj, txPower, bandwidth, centerFreq); % TODO initialize sensor, define parameters [SINR] = sensorPerformance(obj, agentPos, agentPan, agentTilt, targetPos); % determine sensor performance for a given single sensor and target geometry [f] = plotParameters(obj); % debug, plot sensor response as a function of distance and tilt angle end diff --git a/test/test_rfSensor.m b/test/test_rfSensor.m index 61bcd79..c6495a1 100644 --- a/test/test_rfSensor.m +++ b/test/test_rfSensor.m @@ -8,18 +8,28 @@ classdef test_rfSensor < matlab.unittest.TestCase function tc = setup(tc) % Reinitialize sensor with random parameters tc.testClass = rfSensor; - % TODO - tc.testClass = tc.testClass.initialize(); end end methods (Test) - % Test methods + function plot_SNR(tc) + % Plot sensor performance with no sources of interference + P_TX = 1e-3; % Transmit power (Watts) + BW = 20e6; % Bandwidth (Hz) + f_c = 2e9; % Center frequency (Hz) + + tc.testClass = tc.testClass.initialize(P_TX, BW, f_c); - function test_SINR(tc) tc.testClass.plotParameters(); - % [SINR] = tc.testClass.sensorPerformance(obj, agentPos, agentPan, agentTilt, targetPos); end - end + function plot_SINR(tc) + % Plot sensor performance with a single source of interference + P_TX = 1e-3; % Transmit power (Watts) + BW = 20e6; % Bandwidth (Hz) + f_c = 2e9; % Center frequency (Hz) + tc.testClass = tc.testClass.initialize(P_TX, BW, f_c); + + + end end \ No newline at end of file From adac72dbc8148f1cb2fb849a814f036f9c5b6ad5 Mon Sep 17 00:00:00 2001 From: Kevin D Date: Tue, 21 Apr 2026 09:50:07 -0700 Subject: [PATCH 03/32] refactoring for vectorization --- @rfSensor/RSS.m | 8 +++--- @rfSensor/computePointToPoints.m | 21 ++++++++++++++++ @rfSensor/initialize.m | 12 ++++++--- @rfSensor/pathLoss.m | 1 + @rfSensor/plotParameters.m | 6 +---- @rfSensor/rfSensor.m | 4 +++ @rfSensor/sensorPerformance.m | 42 ++++++++++++-------------------- test/test_rfSensor.m | 3 +-- 8 files changed, 55 insertions(+), 42 deletions(-) create mode 100644 @rfSensor/computePointToPoints.m diff --git a/@rfSensor/RSS.m b/@rfSensor/RSS.m index db0fcaf..605e287 100644 --- a/@rfSensor/RSS.m +++ b/@rfSensor/RSS.m @@ -7,10 +7,8 @@ function value = RSS(obj, d, t) arguments (Output) value (:, 1) double end - - rho_dBm = 10*log10(obj.P_TX/1e-3); - - % RSS = TX Power + Antenna Gain - Path Loss - value = rho_dBm + obj.antennaGain(t) - obj.pathLoss(d); + assert(size(d, 1) == size(t, 1), "Mismatch in number of distances (%d) and tilts (%d) provided", size(d, 1), size(t, 1)); + % RSS (dBm) = TX Power (dBm) + Antenna Gain (dB) - Path Loss (dB) + value = obj.P_TX_dBm + obj.antennaGain(t) - obj.pathLoss(d); end \ No newline at end of file diff --git a/@rfSensor/computePointToPoints.m b/@rfSensor/computePointToPoints.m new file mode 100644 index 0000000..1a4e46e --- /dev/null +++ b/@rfSensor/computePointToPoints.m @@ -0,0 +1,21 @@ +function [d, t] = computePointToPoints(obj, agentPos, targetPos) + arguments (Input) + obj (1, 1) {mustBeA(obj, "rfSensor")}; + agentPos (1, 3) double; + targetPos (:, 3) double; + end + arguments (Output) + d (:, 1) double; + t (:, 1) double; + end + + % distance from sensor to target + d = vecnorm(agentPos - targetPos, 2, 2); + + % distance from sensor nadir to target nadir (i.e. distance ignoring altitude) + x = vecnorm(agentPos(1:2) - targetPos(:, 1:2), 2, 2); + + % tilt angle (degrees) + t = (180 - atan2d(x, targetPos(:, 3) - agentPos(3))); + +end \ No newline at end of file diff --git a/@rfSensor/initialize.m b/@rfSensor/initialize.m index 20c3f87..be8dda6 100644 --- a/@rfSensor/initialize.m +++ b/@rfSensor/initialize.m @@ -9,8 +9,12 @@ function obj = initialize(obj, txPower, bandwidth, centerFreq) obj (1, 1) {mustBeA(obj, "rfSensor")} end - obj.P_TX = txPower; - obj.BW = bandwidth; - obj.f_c = centerFreq; - + % Provided values + obj.P_TX = txPower; % Transmit power (W) + obj.BW = bandwidth; % Bandwidth (Hz) + obj.f_c = centerFreq; % Center frequency (Hz) + + % 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 end \ No newline at end of file diff --git a/@rfSensor/pathLoss.m b/@rfSensor/pathLoss.m index 69f51d9..08198d0 100644 --- a/@rfSensor/pathLoss.m +++ b/@rfSensor/pathLoss.m @@ -7,6 +7,7 @@ function L_FSPL_dB = pathLoss(obj, d) L_FSPL_dB (:, 1) double end + % Free Space Path Loss (dB) L_FSPL_dB = 20*log10(d) + 20*log10(obj.f_c) + 20*log10((4*pi)/obj.c); end diff --git a/@rfSensor/plotParameters.m b/@rfSensor/plotParameters.m index 1a39138..3d008a4 100644 --- a/@rfSensor/plotParameters.m +++ b/@rfSensor/plotParameters.m @@ -9,17 +9,13 @@ function f = plotParameters(obj) % Distance and tilt sample points d = [0.01, 0.1, 0.25, 0.5, 0.75, 1:1:100]; t = zeros(size(d)); - t = -90:1:90; % Sample RSS function by distances, tilts r_x = obj.RSS(d', t'); % Sample SINR (SNR) function by distances, tilts % using SINR method with no other transmitters defined is equivalent to SNR - s_x = NaN(size(r_x)); - for ii = 1:size(s_x, 1) - s_x(ii) = obj.sensorPerformance([0, 0, d(ii)], zeros(1, 3)); % don't define other sensors - end + s_x = obj.sensorPerformance(d, t); % don't define other sensors % Plot resultant sigmoid curves f = figure; diff --git a/@rfSensor/rfSensor.m b/@rfSensor/rfSensor.m index 9759fd0..9d56285 100644 --- a/@rfSensor/rfSensor.m +++ b/@rfSensor/rfSensor.m @@ -8,12 +8,16 @@ classdef rfSensor P_TX = NaN; % Transmit power (Watts) BW = NaN; % Bandwidth (Hz) f_c = NaN; % Center frequency (Hz) + % Values computed at initialization + P_TX_dBm = NaN; % Transmit power (dBm) + N = NaN; % Thermal noise end methods (Access = public) [obj] = initialize(obj, txPower, bandwidth, centerFreq); % TODO initialize sensor, define parameters [SINR] = sensorPerformance(obj, agentPos, agentPan, agentTilt, targetPos); % determine sensor performance for a given single sensor and target geometry [f] = plotParameters(obj); % debug, plot sensor response as a function of distance and tilt angle + [d, t] = computePointToPoints(obj, agentPos, targetPos); end methods (Access = private) x = RSS(obj, d, t); % Received signal strength (function of distance and tilt angle) diff --git a/@rfSensor/sensorPerformance.m b/@rfSensor/sensorPerformance.m index c88d7d5..be6d329 100644 --- a/@rfSensor/sensorPerformance.m +++ b/@rfSensor/sensorPerformance.m @@ -1,36 +1,26 @@ -function SINR = sensorPerformance(obj, agentPos, targetPos, otherSensors, otherSensorsPos) +function SINR = sensorPerformance(obj, d, t, d_other, t_other, otherSensors) arguments (Input) obj (1, 1) {mustBeA(obj, "rfSensor")}; - agentPos (1, 3) double; - targetPos (1, 3) double; - otherSensors (:, 1) cell = {}; - otherSensorsPos (:, 3) double = NaN(0, 3); + d (:, 1) double; + t(:, 1) double; + d_other (:, :) double = []; + t_other (:, :) double = []; + otherSensors (1, :) cell = {}; end arguments (Output) SINR (:, 1) double; end - assert(size(otherSensors, 1) == size(otherSensorsPos, 1), "Mismatch in length of other sensors (%d) and their positions (%d)", size(otherSensors, 1), size(otherSensorsPos, 1)); - - d = vecnorm(agentPos - targetPos, 2, 2); % distance from sensor to target - d_other = NaN(size(otherSensors)); - - x = vecnorm(agentPos(1:2) - targetPos(1, 1:2), 2, 2); % distance from sensor nadir to target nadir (i.e. distance ignoring height difference) - x_other = NaN(size(otherSensors)); - - t = (180 - atan2d(x, targetPos(1, 3) - agentPos(3))); % degrees - t_other = NaN(size(otherSensors)); + assert(size(d, 1) == size(t, 1), "Mismatch in number of distance (%d) and angle (%d) pairs provided", size(d, 1), size(t, 1)); + assert(size(d_other, 1) == size(t_other, 1), "Mismatch in number of distances (%d) and tilts (%d) provided to other sensors", size(t, 1), size(t_other, 1)); + assert(size(d_other, 2) == size(t_other, 2), "Mismatch in number of other sensors given distances (%d) and tilts (%d)", size(d_other, 1), size(t_other, 1)); + assert(size(otherSensors, 2) == size(d_other, 2), "Mismatch in number of distances from other sensors (%d) and number of other sensors (%d) provided", size(d_other, 2), size(otherSensors, 2)); % Performance is measured as SINR for this sensor - S = 10^(0.1 * obj.RSS(d, t)); % Signal - N = obj.k_B * obj.T_0 * obj.BW; % Thermal noise - I = 0; % Interference from other agents - for ii = 1:size(otherSensors, 1) - d_other(ii, 1) = vecnorm(otherSensorsPos(ii, 1:3) - targetPos, 2, 2); - - x_other(ii, 1) = vecnorm(otherSensorsPos(ii, 3) - targetPos(1, 1:2), 2, 2); - t_other(ii, 1) = (180 - atan2d(x_other(ii, 1), targetPos(1, 3) - otherSensorsPos(ii, 3))); - - I = I + 10 ^ (0.1 * otherSensors.RSS(otherSensors{ii}, d_other(ii), t_other(ii))); + S = 10 .^ (0.1 * obj.RSS(d, t)); % Signal + I = zeros(size(d)); % Interference from other agents + for ii = 1:size(otherSensors, 2) + I = I + 10 .^ (0.1 * otherSensors{ii}.RSS(d_other(:, ii), t_other(:, ii))); end - SINR = 10*log10(S/(I + N)); + + SINR = 10*log10(S ./ (I + obj.N)); end \ No newline at end of file diff --git a/test/test_rfSensor.m b/test/test_rfSensor.m index c6495a1..621002e 100644 --- a/test/test_rfSensor.m +++ b/test/test_rfSensor.m @@ -29,7 +29,6 @@ classdef test_rfSensor < matlab.unittest.TestCase f_c = 2e9; % Center frequency (Hz) tc.testClass = tc.testClass.initialize(P_TX, BW, f_c); - - + end end end \ No newline at end of file From fbc7fe18f48ef396e84c9ffb982c4c02d88e24f1 Mon Sep 17 00:00:00 2001 From: Kevin D Date: Tue, 21 Apr 2026 10:07:50 -0700 Subject: [PATCH 04/32] improved rfSensor response plotting --- @rfSensor/antennaGain.m | 8 ++++++-- @rfSensor/plotParameters.m | 19 +++++++++++++------ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/@rfSensor/antennaGain.m b/@rfSensor/antennaGain.m index 4fdd4e7..b34dfb3 100644 --- a/@rfSensor/antennaGain.m +++ b/@rfSensor/antennaGain.m @@ -7,7 +7,11 @@ function value = antennaGain(obj, t) value (:, 1) double end - % TODO + %% TODO + % Temporary logic to make nadir-pointing most effective - value = 10*log10(cosd(t)); + value = 10*log10(cosd(t) .^ 8); + + % % Temporary logic for 0 dB at all tilt angles + % value = zeros(size(t)); end diff --git a/@rfSensor/plotParameters.m b/@rfSensor/plotParameters.m index 3d008a4..99dec45 100644 --- a/@rfSensor/plotParameters.m +++ b/@rfSensor/plotParameters.m @@ -7,8 +7,13 @@ function f = plotParameters(obj) end % Distance and tilt sample points - d = [0.01, 0.1, 0.25, 0.5, 0.75, 1:1:100]; - t = zeros(size(d)); + d_values = [0.01, 0.1, 0.25, 0.5, 0.75, 1, 2, 3, 4, 5:5:100]; + % t = zeros(size(d)); + t_values = -90:15:90; + + % Make grid of values of distances and tilts + [d_mg, t_mg] = meshgrid(d_values, t_values); + d = d_mg(:); t = t_mg(:); % flatten % Sample RSS function by distances, tilts r_x = obj.RSS(d', t'); @@ -26,9 +31,10 @@ function f = plotParameters(obj) grid("on"); title("RSS vs Distance"); xlabel("Distance (m)"); - ylabel("RSS (dBm)"); + ylabel("Tilt (deg)"); + zlabel("RSS (dBm)"); hold("on"); - plot(d, r_x, "LineWidth", 2); + surf(d_mg, t_mg, reshape(r_x, size(d_mg))); hold("off"); % ylim([0, 1]); @@ -37,8 +43,9 @@ function f = plotParameters(obj) grid("on"); title("SNR vs Distance"); xlabel("Distance (m)"); - ylabel("SNR (dB)"); + ylabel("Tilt (deg)"); + zlabel("SNR (dB)"); hold("on"); - plot(d, s_x, "LineWidth", 2); + surf(d_mg, t_mg, reshape(s_x, size(d_mg))); hold("off"); end \ No newline at end of file From c467ca35be2b4d8443bf0767cab883b10346c2aa Mon Sep 17 00:00:00 2001 From: Kevin D Date: Tue, 21 Apr 2026 10:32:56 -0700 Subject: [PATCH 05/32] cleanup --- @rfSensor/plotParameters.m | 23 +---------------------- test/test_rfSensor.m | 8 -------- 2 files changed, 1 insertion(+), 30 deletions(-) diff --git a/@rfSensor/plotParameters.m b/@rfSensor/plotParameters.m index 99dec45..89623d5 100644 --- a/@rfSensor/plotParameters.m +++ b/@rfSensor/plotParameters.m @@ -5,7 +5,7 @@ function f = plotParameters(obj) arguments (Output) f (1, 1) {mustBeA(f, "matlab.ui.Figure")}; end - + % Distance and tilt sample points d_values = [0.01, 0.1, 0.25, 0.5, 0.75, 1, 2, 3, 4, 5:5:100]; % t = zeros(size(d)); @@ -15,37 +15,16 @@ function f = plotParameters(obj) [d_mg, t_mg] = meshgrid(d_values, t_values); d = d_mg(:); t = t_mg(:); % flatten - % Sample RSS function by distances, tilts - r_x = obj.RSS(d', t'); - % Sample SINR (SNR) function by distances, tilts % using SINR method with no other transmitters defined is equivalent to SNR s_x = obj.sensorPerformance(d, t); % don't define other sensors % Plot resultant sigmoid curves f = figure; - tiledlayout(f, 2, 1, "TileSpacing", "tight", "Padding", "compact"); - - % RSS/Distance with 0 tilt - nexttile(1, [1, 1]); - grid("on"); - title("RSS vs Distance"); - xlabel("Distance (m)"); - ylabel("Tilt (deg)"); - zlabel("RSS (dBm)"); - hold("on"); - surf(d_mg, t_mg, reshape(r_x, size(d_mg))); - hold("off"); - % ylim([0, 1]); - - % SNR/Distance with 0 tilt - nexttile(2, [1, 1]); grid("on"); title("SNR vs Distance"); xlabel("Distance (m)"); ylabel("Tilt (deg)"); zlabel("SNR (dB)"); - hold("on"); surf(d_mg, t_mg, reshape(s_x, size(d_mg))); - hold("off"); end \ No newline at end of file diff --git a/test/test_rfSensor.m b/test/test_rfSensor.m index 621002e..1819401 100644 --- a/test/test_rfSensor.m +++ b/test/test_rfSensor.m @@ -22,13 +22,5 @@ classdef test_rfSensor < matlab.unittest.TestCase tc.testClass.plotParameters(); end - function plot_SINR(tc) - % Plot sensor performance with a single source of interference - P_TX = 1e-3; % Transmit power (Watts) - BW = 20e6; % Bandwidth (Hz) - f_c = 2e9; % Center frequency (Hz) - - tc.testClass = tc.testClass.initialize(P_TX, BW, f_c); - end end end \ No newline at end of file From 69e11549b2a52c9368397784702d3145c4eb6343 Mon Sep 17 00:00:00 2001 From: Kevin D Date: Tue, 21 Apr 2026 10:33:48 -0700 Subject: [PATCH 06/32] plot fix --- @rfSensor/plotParameters.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/@rfSensor/plotParameters.m b/@rfSensor/plotParameters.m index 89623d5..7521464 100644 --- a/@rfSensor/plotParameters.m +++ b/@rfSensor/plotParameters.m @@ -22,9 +22,9 @@ function f = plotParameters(obj) % Plot resultant sigmoid curves f = figure; grid("on"); - title("SNR vs Distance"); + surf(d_mg, t_mg, reshape(s_x, size(d_mg))); + title("SNR vs Distance and Tilt"); xlabel("Distance (m)"); ylabel("Tilt (deg)"); zlabel("SNR (dB)"); - surf(d_mg, t_mg, reshape(s_x, size(d_mg))); end \ No newline at end of file From 6cb6dabcb56765924471f03d855a2a902734d866 Mon Sep 17 00:00:00 2001 From: Kevin D Date: Wed, 22 Apr 2026 08:16:20 -0700 Subject: [PATCH 07/32] plotted 0 tilt SNR over range --- @rfSensor/plotParameters.m | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/@rfSensor/plotParameters.m b/@rfSensor/plotParameters.m index 7521464..579589c 100644 --- a/@rfSensor/plotParameters.m +++ b/@rfSensor/plotParameters.m @@ -18,11 +18,19 @@ function f = plotParameters(obj) % Sample SINR (SNR) function by distances, tilts % using SINR method with no other transmitters defined is equivalent to SNR s_x = obj.sensorPerformance(d, t); % don't define other sensors + s_x = reshape(s_x, size(d_mg)); % Plot resultant sigmoid curves - f = figure; + figure; + plot(d_values.', s_x(repmat((t_values == 0).', 1, size(d_values, 2))), "LineWidth", 2); + grid("on"); + title("SNR vs Distance at 0 tilt"); + xlabel("Distance (m)"); + ylabel("SNR (dB)"); + + figure; + surf(d_mg, t_mg, s_x); grid("on"); - surf(d_mg, t_mg, reshape(s_x, size(d_mg))); title("SNR vs Distance and Tilt"); xlabel("Distance (m)"); ylabel("Tilt (deg)"); From d07df25528358f29ced9e06725f42949f609e9d3 Mon Sep 17 00:00:00 2001 From: Kevin D Date: Sun, 26 Apr 2026 10:28:28 -0700 Subject: [PATCH 08/32] RF antenna azimuth, plotting improvements --- @rfSensor/RSS.m | 7 ++- @rfSensor/antennaGain.m | 17 ------ @rfSensor/computePointToPoints.m | 7 ++- @rfSensor/initialize.m | 4 +- @rfSensor/plotParameters.m | 57 ++++++++++++------- @rfSensor/rfSensor.m | 13 +++-- @rfSensor/sensorPerformance.m | 23 ++++---- @rfSensor/transmitterGain.m | 16 ++++++ @sigmoidSensor/sigmoidSensor.m | 2 +- .../0TUOPB3gylGuEK1Q5NsFsa57FBQp.xml | 2 - ...d.xml => Y0nVGErZr2RP2C5DDHiHUMtLRnod.xml} | 0 .../Y0nVGErZr2RP2C5DDHiHUMtLRnop.xml | 2 + test/test_rfSensor.m | 3 +- 13 files changed, 88 insertions(+), 65 deletions(-) delete mode 100644 @rfSensor/antennaGain.m create mode 100644 @rfSensor/transmitterGain.m delete mode 100644 resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/0TUOPB3gylGuEK1Q5NsFsa57FBQp.xml rename resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/{0TUOPB3gylGuEK1Q5NsFsa57FBQd.xml => Y0nVGErZr2RP2C5DDHiHUMtLRnod.xml} (100%) create mode 100644 resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/Y0nVGErZr2RP2C5DDHiHUMtLRnop.xml diff --git a/@rfSensor/RSS.m b/@rfSensor/RSS.m index 605e287..c512a56 100644 --- a/@rfSensor/RSS.m +++ b/@rfSensor/RSS.m @@ -1,14 +1,15 @@ -function value = RSS(obj, d, t) +function value = RSS(obj, d, t, a) arguments (Input) obj (1, 1) {mustBeA(obj, "rfSensor")}; d (:, 1) double; % distance from agent to target t (:, 1) double; % LOS tilt angle + a (:, 1) double; % LOS azimuth angle end arguments (Output) value (:, 1) double end assert(size(d, 1) == size(t, 1), "Mismatch in number of distances (%d) and tilts (%d) provided", size(d, 1), size(t, 1)); - % RSS (dBm) = TX Power (dBm) + Antenna Gain (dB) - Path Loss (dB) - value = obj.P_TX_dBm + obj.antennaGain(t) - obj.pathLoss(d); + % RSS (dBm) = TX Power (dBm) + TX Antenna Gain (dB) + RX Antenna Gain (dBi) - Path Loss (dB) + value = obj.P_TX_dBm + obj.transmitterGain(t, a) + obj.G_RX_dBi - obj.pathLoss(d); end \ No newline at end of file diff --git a/@rfSensor/antennaGain.m b/@rfSensor/antennaGain.m deleted file mode 100644 index b34dfb3..0000000 --- a/@rfSensor/antennaGain.m +++ /dev/null @@ -1,17 +0,0 @@ -function value = antennaGain(obj, t) - arguments (Input) - obj (1, 1) {mustBeA(obj, "rfSensor")}; - t (:, 1) double; % LOS tilt angle - end - arguments (Output) - value (:, 1) double - end - - %% TODO - - % Temporary logic to make nadir-pointing most effective - value = 10*log10(cosd(t) .^ 8); - - % % Temporary logic for 0 dB at all tilt angles - % value = zeros(size(t)); -end diff --git a/@rfSensor/computePointToPoints.m b/@rfSensor/computePointToPoints.m index 1a4e46e..fcf36ca 100644 --- a/@rfSensor/computePointToPoints.m +++ b/@rfSensor/computePointToPoints.m @@ -1,4 +1,4 @@ -function [d, t] = computePointToPoints(obj, agentPos, targetPos) +function [d, t, a] = computePointToPoints(obj, agentPos, targetPos) arguments (Input) obj (1, 1) {mustBeA(obj, "rfSensor")}; agentPos (1, 3) double; @@ -7,6 +7,7 @@ function [d, t] = computePointToPoints(obj, agentPos, targetPos) arguments (Output) d (:, 1) double; t (:, 1) double; + a (:, 1) double; end % distance from sensor to target @@ -15,7 +16,9 @@ function [d, t] = computePointToPoints(obj, agentPos, targetPos) % distance from sensor nadir to target nadir (i.e. distance ignoring altitude) x = vecnorm(agentPos(1:2) - targetPos(:, 1:2), 2, 2); - % tilt angle (degrees) + % tilt angle (degrees) (-90, 0 (down), 90) t = (180 - atan2d(x, targetPos(:, 3) - agentPos(3))); + % azimuth angle (degrees) (0 (+y) clockwise to 360) + a = mod(atan2d(targetPos(:,1) - agentPos(1), targetPos(:,2) - agentPos(2)), 360); end \ No newline at end of file diff --git a/@rfSensor/initialize.m b/@rfSensor/initialize.m index be8dda6..9dfb966 100644 --- a/@rfSensor/initialize.m +++ b/@rfSensor/initialize.m @@ -1,9 +1,10 @@ -function obj = initialize(obj, txPower, bandwidth, centerFreq) +function obj = initialize(obj, txPower, bandwidth, centerFreq, rxGain_dBi) arguments (Input) obj (1, 1) {mustBeA(obj, "rfSensor")} txPower (1, 1) double; bandwidth (1, 1) double; centerFreq (1, 1) double; + rxGain_dBi (1, 1) double; end arguments (Output) obj (1, 1) {mustBeA(obj, "rfSensor")} @@ -13,6 +14,7 @@ function obj = initialize(obj, txPower, bandwidth, centerFreq) obj.P_TX = txPower; % Transmit power (W) obj.BW = bandwidth; % Bandwidth (Hz) obj.f_c = centerFreq; % Center frequency (Hz) + obj.G_RX_dBi = rxGain_dBi; % Receiving Antenna Gain (dBi) % Computed values obj.P_TX_dBm = 10*log10(obj.P_TX/1e-3); % Transmit power in dBm diff --git a/@rfSensor/plotParameters.m b/@rfSensor/plotParameters.m index 579589c..e4c5778 100644 --- a/@rfSensor/plotParameters.m +++ b/@rfSensor/plotParameters.m @@ -7,32 +7,49 @@ function f = plotParameters(obj) end % Distance and tilt sample points - d_values = [0.01, 0.1, 0.25, 0.5, 0.75, 1, 2, 3, 4, 5:5:100]; - % t = zeros(size(d)); - t_values = -90:15:90; + d_values = [0.1, 0.5, 1:1:9, 10:2:19, 20:5:49, 50:10:100]; + t_values = -90:0.5:90; + a_values = 0:0.5:360; % Make grid of values of distances and tilts - [d_mg, t_mg] = meshgrid(d_values, t_values); - d = d_mg(:); t = t_mg(:); % flatten + [d_mg, t_mg, a_mg] = meshgrid(d_values, t_values, a_values); + d = d_mg(:); t = t_mg(:); a = a_mg(:); % flatten - % Sample SINR (SNR) function by distances, tilts - % using SINR method with no other transmitters defined is equivalent to SNR - s_x = obj.sensorPerformance(d, t); % don't define other sensors + % Sample received signal strength (no interference or noise) + s_x = obj.RSS(d, t, a); s_x = reshape(s_x, size(d_mg)); - % Plot resultant sigmoid curves + [T, A] = meshgrid(t_values, a_values); % Naz x Nel + Tr = deg2rad(T); + Ar = deg2rad(A); + figure; - plot(d_values.', s_x(repmat((t_values == 0).', 1, size(d_values, 2))), "LineWidth", 2); - grid("on"); - title("SNR vs Distance at 0 tilt"); - xlabel("Distance (m)"); - ylabel("SNR (dB)"); + hold("on"); - figure; - surf(d_mg, t_mg, s_x); + for ii = 1:numel(d_values) + % geometry (your "tilt from nadir, stack by distance") + X = d_values(ii) * cos(Ar) .* sin(Tr); + Y = d_values(ii) * sin(Ar) .* sin(Tr); + Z = d_values(ii) * ones(size(X)); + + % evaluate or extract this slice + Fslice = squeeze(s_x(:, ii, :))'; + + % plot as its own surface + h = surf(X, Y, Z, Fslice); + h.EdgeColor = 'none'; + h.FaceColor = 'interp'; + h.FaceAlpha = 0.25; + end + + colormap(turbo); + colorbar; + daspect([1 1 0.2]) % Separate Z further for more distinct layers + xlabel('X'); ylabel('Y'); zlabel('Distance (m)'); + set(gca,'ZDir','reverse'); + view(3); + axis("vis3d"); grid("on"); - title("SNR vs Distance and Tilt"); - xlabel("Distance (m)"); - ylabel("Tilt (deg)"); - zlabel("SNR (dB)"); + scatter3(0, 0, 0, 'rx'); + hold("off"); end \ No newline at end of file diff --git a/@rfSensor/rfSensor.m b/@rfSensor/rfSensor.m index 9d56285..14381e2 100644 --- a/@rfSensor/rfSensor.m +++ b/@rfSensor/rfSensor.m @@ -8,20 +8,21 @@ classdef rfSensor P_TX = NaN; % Transmit power (Watts) BW = NaN; % Bandwidth (Hz) f_c = NaN; % Center frequency (Hz) + G_RX_dBi = NaN; % Receiver antenna gain % Values computed at initialization P_TX_dBm = NaN; % Transmit power (dBm) N = NaN; % Thermal noise end methods (Access = public) - [obj] = initialize(obj, txPower, bandwidth, centerFreq); % TODO initialize sensor, define parameters - [SINR] = sensorPerformance(obj, agentPos, agentPan, agentTilt, targetPos); % determine sensor performance for a given single sensor and target geometry + [obj] = initialize(obj, txPower, bandwidth, centerFreq, rxGain); % initialize sensor, define parameters + [SINR] = sensorPerformance(obj, agentPos, targetPos, otherSensorsPos, otherSensors); % determine sensor performance for a given single sensor and target geometry [f] = plotParameters(obj); % debug, plot sensor response as a function of distance and tilt angle - [d, t] = computePointToPoints(obj, agentPos, targetPos); + [d, t, a] = computePointToPoints(obj, agentPos, targetPos); end methods (Access = private) - x = RSS(obj, d, t); % Received signal strength (function of distance and tilt angle) - G_TX_dB = antennaGain(obj, agentPos, targetPos); % TODO Antenna gain for a given TX/RX pair - L_FSPL_dB = pathLoss(obj, agentPos, targetPos); % Free space path loss for a given TX/RX pair + x = RSS(obj, d, t, a); % Received signal strength (function of distance and tilt angle) + G_TX_dB = transmitterGain(obj, t, a); % TODO Antenna gain for a given TX/RX pair + 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/sensorPerformance.m b/@rfSensor/sensorPerformance.m index be6d329..9e35fff 100644 --- a/@rfSensor/sensorPerformance.m +++ b/@rfSensor/sensorPerformance.m @@ -1,25 +1,24 @@ -function SINR = sensorPerformance(obj, d, t, d_other, t_other, otherSensors) +function SINR = sensorPerformance(obj, agentPos, targetPos, otherSensorsPos, otherSensors) arguments (Input) obj (1, 1) {mustBeA(obj, "rfSensor")}; - d (:, 1) double; - t(:, 1) double; - d_other (:, :) double = []; - t_other (:, :) double = []; - otherSensors (1, :) cell = {}; + agentPos (1, 3) double; + targetPos (:, 3) double; + otherSensorsPos (:, 3) double = []; + otherSensors (:, 1) cell = {}; end arguments (Output) SINR (:, 1) double; end - assert(size(d, 1) == size(t, 1), "Mismatch in number of distance (%d) and angle (%d) pairs provided", size(d, 1), size(t, 1)); - assert(size(d_other, 1) == size(t_other, 1), "Mismatch in number of distances (%d) and tilts (%d) provided to other sensors", size(t, 1), size(t_other, 1)); - assert(size(d_other, 2) == size(t_other, 2), "Mismatch in number of other sensors given distances (%d) and tilts (%d)", size(d_other, 1), size(t_other, 1)); - assert(size(otherSensors, 2) == size(d_other, 2), "Mismatch in number of distances from other sensors (%d) and number of other sensors (%d) provided", size(d_other, 2), size(otherSensors, 2)); + assert(size(otherSensorsPos, 1) == size(otherSensors, 1), "Mismatch in number of other sensor positions (%d) and number of other sensors (%d) provided", size(otherSensorsPos, 1), size(otherSensors, 1)); + + [d, t, a] = obj.computePointToPoints(agentPos, targetPos); % Performance is measured as SINR for this sensor - S = 10 .^ (0.1 * obj.RSS(d, t)); % Signal + S = 10 .^ (0.1 .* obj.RSS(d, t, a)); % Signal I = zeros(size(d)); % Interference from other agents for ii = 1:size(otherSensors, 2) - I = I + 10 .^ (0.1 * otherSensors{ii}.RSS(d_other(:, ii), t_other(:, ii))); + [d_other, t_other, a_other] = otherSensors{ii}.computePointToPoints(otherSensorsPos(ii, 1:3), targetPos); + I = I + 10 .^ (0.1 .* otherSensors{ii}.RSS(d_other, t_other, a_other)); end SINR = 10*log10(S ./ (I + obj.N)); diff --git a/@rfSensor/transmitterGain.m b/@rfSensor/transmitterGain.m new file mode 100644 index 0000000..2b7a0c6 --- /dev/null +++ b/@rfSensor/transmitterGain.m @@ -0,0 +1,16 @@ +function value = transmitterGain(obj, t, a) + arguments (Input) + obj (1, 1) {mustBeA(obj, "rfSensor")}; + t (:, 1) double; % LOS tilt angle + a (:, 1) double; % LOS azimuth angle + end + arguments (Output) + value (:, 1) double + end + if ~isequal(size(t), size(a)) + error("t and a must be the same size"); + end + + % Temporary logic to make nadir-pointing most effective + value = 10 .* log10(cosd(t) .^ 2) + 10 .* log10((0.5 + 0.5 .* cosd(a)) .^ 4); +end diff --git a/@sigmoidSensor/sigmoidSensor.m b/@sigmoidSensor/sigmoidSensor.m index e25bd79..958e9f2 100644 --- a/@sigmoidSensor/sigmoidSensor.m +++ b/@sigmoidSensor/sigmoidSensor.m @@ -9,7 +9,7 @@ classdef sigmoidSensor methods (Access = public) [obj] = initialize(obj, alphaDist, betaDist, alphaTilt, betaTilt); % initialize sensor, define parameters - [value] = sensorPerformance(obj, agentPos, agentPan, agentTilt, targetPos); % determine sensor performance for a given single sensor and target geometry + [value] = sensorPerformance(obj, agentPos, targetPos); % determine sensor performance for a given single sensor and target geometry [f] = plotParameters(obj); % debug, plot sensor response as a function of distance and tilt angle end methods (Access = private) diff --git a/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/0TUOPB3gylGuEK1Q5NsFsa57FBQp.xml b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/0TUOPB3gylGuEK1Q5NsFsa57FBQp.xml deleted file mode 100644 index 4401f9c..0000000 --- a/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/0TUOPB3gylGuEK1Q5NsFsa57FBQp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/0TUOPB3gylGuEK1Q5NsFsa57FBQd.xml b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/Y0nVGErZr2RP2C5DDHiHUMtLRnod.xml similarity index 100% rename from resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/0TUOPB3gylGuEK1Q5NsFsa57FBQd.xml rename to resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/Y0nVGErZr2RP2C5DDHiHUMtLRnod.xml diff --git a/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/Y0nVGErZr2RP2C5DDHiHUMtLRnop.xml b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/Y0nVGErZr2RP2C5DDHiHUMtLRnop.xml new file mode 100644 index 0000000..d3618f2 --- /dev/null +++ b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/Y0nVGErZr2RP2C5DDHiHUMtLRnop.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/test/test_rfSensor.m b/test/test_rfSensor.m index 1819401..2cafc6f 100644 --- a/test/test_rfSensor.m +++ b/test/test_rfSensor.m @@ -17,8 +17,9 @@ classdef test_rfSensor < matlab.unittest.TestCase P_TX = 1e-3; % Transmit power (Watts) BW = 20e6; % Bandwidth (Hz) f_c = 2e9; % Center frequency (Hz) + G_RX_dBi = 3; % Receiving Antenna Gain (dBi) - tc.testClass = tc.testClass.initialize(P_TX, BW, f_c); + tc.testClass = tc.testClass.initialize(P_TX, BW, f_c, G_RX_dBi); tc.testClass.plotParameters(); end From 5cbb3956840a06856b40fc17244db67a1590adc3 Mon Sep 17 00:00:00 2001 From: Kevin D Date: Sun, 26 Apr 2026 10:59:30 -0700 Subject: [PATCH 09/32] added SINR visualization --- @rfSensor/plotPerformance.m | 41 +++++++++++++++++++++++++++++++++++ @rfSensor/rfSensor.m | 5 +++-- @rfSensor/sensorPerformance.m | 6 +++-- test/test_rfSensor.m | 32 ++++++++++++++++++++++++++- 4 files changed, 79 insertions(+), 5 deletions(-) create mode 100644 @rfSensor/plotPerformance.m diff --git a/@rfSensor/plotPerformance.m b/@rfSensor/plotPerformance.m new file mode 100644 index 0000000..d5cc38c --- /dev/null +++ b/@rfSensor/plotPerformance.m @@ -0,0 +1,41 @@ +function f = plotPerformance(obj, altitude, otherSensorsPos, otherSensors) + arguments (Input) + obj (1, 1) {mustBeA(obj, "rfSensor")}; + altitude (1, 1) double; + otherSensorsPos (:, 3) double = NaN(0, 3); + otherSensors (:, 1) cell = cell(0, 1); + end + arguments (Output) + f (1, 1) {mustBeA(f, "matlab.ui.Figure")}; + end + + % Create grid on which to evalute SINR, SNR + agentPos = [0, 0, altitude]; + d = max(10, max(vecnorm(otherSensorsPos(1:2), 2, 2)) * 1.25); + c = 0.1; + d = ceil(d / c) * c; + distances = -d:c:d; + [targetPosX, targetPosY] = meshgrid(distances, distances); + + % Compute SINR, SNR + [SINR, SNR] = obj.sensorPerformance(agentPos, [targetPosX(:), targetPosY(:), zeros(size(targetPosX(:)))], otherSensorsPos, otherSensors); + SINR = reshape(SINR, size(targetPosX)); + SNR = reshape(SNR, size(targetPosX)); + + % normalize + SINR = SINR ./ max(SINR); + SNR = SNR ./ max(SNR); + + f = figure; + imagesc(SNR); + axis("image"); + colorbar; + title("Normalized SNR"); + + f = figure; + imagesc(SINR); + axis("image"); + colorbar; + title("Normalized SINR"); + +end \ No newline at end of file diff --git a/@rfSensor/rfSensor.m b/@rfSensor/rfSensor.m index 14381e2..20ac328 100644 --- a/@rfSensor/rfSensor.m +++ b/@rfSensor/rfSensor.m @@ -16,9 +16,10 @@ classdef rfSensor methods (Access = public) [obj] = initialize(obj, txPower, bandwidth, centerFreq, rxGain); % initialize sensor, define parameters - [SINR] = sensorPerformance(obj, agentPos, targetPos, otherSensorsPos, otherSensors); % determine sensor performance for a given single sensor and target geometry - [f] = plotParameters(obj); % debug, plot sensor response as a function of distance and tilt angle + [SINR, SNR] = sensorPerformance(obj, agentPos, targetPos, otherSensorsPos, otherSensors); % determine sensor performance for a given single sensor and target geometry [d, t, a] = computePointToPoints(obj, agentPos, targetPos); + [f] = plotParameters(obj); % debug, plot sensor response as a function of distance and tilt angle + [f] = plotPerformance(obj, altitude, otherSensorsPos, otherSensors); % debug, plot SNR or SINR ground heatmap for a given geometry end methods (Access = private) x = RSS(obj, d, t, a); % Received signal strength (function of distance and tilt angle) diff --git a/@rfSensor/sensorPerformance.m b/@rfSensor/sensorPerformance.m index 9e35fff..53f83e2 100644 --- a/@rfSensor/sensorPerformance.m +++ b/@rfSensor/sensorPerformance.m @@ -1,4 +1,4 @@ -function SINR = sensorPerformance(obj, agentPos, targetPos, otherSensorsPos, otherSensors) +function [SINR, SNR] = sensorPerformance(obj, agentPos, targetPos, otherSensorsPos, otherSensors) arguments (Input) obj (1, 1) {mustBeA(obj, "rfSensor")}; agentPos (1, 3) double; @@ -8,6 +8,7 @@ function SINR = sensorPerformance(obj, agentPos, targetPos, otherSensorsPos, oth end arguments (Output) SINR (:, 1) double; + SNR (:, 1) double; end assert(size(otherSensorsPos, 1) == size(otherSensors, 1), "Mismatch in number of other sensor positions (%d) and number of other sensors (%d) provided", size(otherSensorsPos, 1), size(otherSensors, 1)); @@ -16,10 +17,11 @@ function SINR = sensorPerformance(obj, agentPos, targetPos, otherSensorsPos, oth % Performance is measured as SINR for this sensor S = 10 .^ (0.1 .* obj.RSS(d, t, a)); % Signal I = zeros(size(d)); % Interference from other agents - for ii = 1:size(otherSensors, 2) + for ii = 1:size(otherSensors, 1) [d_other, t_other, a_other] = otherSensors{ii}.computePointToPoints(otherSensorsPos(ii, 1:3), targetPos); I = I + 10 .^ (0.1 .* otherSensors{ii}.RSS(d_other, t_other, a_other)); end SINR = 10*log10(S ./ (I + obj.N)); + SNR = 10*log10(S ./ obj.N); end \ No newline at end of file diff --git a/test/test_rfSensor.m b/test/test_rfSensor.m index 2cafc6f..5f92eb3 100644 --- a/test/test_rfSensor.m +++ b/test/test_rfSensor.m @@ -12,7 +12,7 @@ classdef test_rfSensor < matlab.unittest.TestCase end methods (Test) - function plot_SNR(tc) + function plot_RSS(tc) % Plot sensor performance with no sources of interference P_TX = 1e-3; % Transmit power (Watts) BW = 20e6; % Bandwidth (Hz) @@ -23,5 +23,35 @@ classdef test_rfSensor < matlab.unittest.TestCase tc.testClass.plotParameters(); end + function plot_SNR(tc) + % Plot sensor performance with no sources of interference + P_TX = 1e-3; % Transmit power (Watts) + BW = 20e6; % Bandwidth (Hz) + f_c = 2e9; % Center frequency (Hz) + G_RX_dBi = 3; % Receiving Antenna Gain (dBi) + + tc.testClass = tc.testClass.initialize(P_TX, BW, f_c, G_RX_dBi); + + altitude = 30; + + tc.testClass.plotPerformance(altitude); + end + function plot_SINR_one_interferer(tc) + % Plot sensor performance with no sources of interference + P_TX = 1e-3; % Transmit power (Watts) + BW = 20e6; % Bandwidth (Hz) + f_c = 2e9; % Center frequency (Hz) + G_RX_dBi = 3; % Receiving Antenna Gain (dBi) + + tc.testClass = tc.testClass.initialize(P_TX, BW, f_c, G_RX_dBi); + + altitude = 30; + otherSensorsPos = [5, 0, 400]; % relative to main sensor + otherSensors = cell(1, 1); + otherSensors{1} = tc.testClass; % 2 identical sensors + + tc.testClass.plotPerformance(altitude, otherSensorsPos, otherSensors); + + end end end \ No newline at end of file From e19e9870d7f7d0bd5b49a86ffcbd8728c3468a7b Mon Sep 17 00:00:00 2001 From: Kevin D Date: Sun, 26 Apr 2026 11:46:25 -0700 Subject: [PATCH 10/32] added SNR and SINR footprint images --- @rfSensor/pathLoss.m | 4 ++-- @rfSensor/plotPerformance.m | 29 ++++++++++++++++++----------- test/test_rfSensor.m | 2 +- 3 files changed, 21 insertions(+), 14 deletions(-) diff --git a/@rfSensor/pathLoss.m b/@rfSensor/pathLoss.m index 08198d0..c638699 100644 --- a/@rfSensor/pathLoss.m +++ b/@rfSensor/pathLoss.m @@ -7,7 +7,7 @@ function L_FSPL_dB = pathLoss(obj, d) L_FSPL_dB (:, 1) double end - % Free Space Path Loss (dB) - L_FSPL_dB = 20*log10(d) + 20*log10(obj.f_c) + 20*log10((4*pi)/obj.c); + % Free Space Path Loss (dB); d clamped away from zero (log undefined at d=0) + L_FSPL_dB = 20*log10(max(d, eps)) + 20*log10(obj.f_c) + 20*log10((4*pi)/obj.c); end diff --git a/@rfSensor/plotPerformance.m b/@rfSensor/plotPerformance.m index d5cc38c..f44b77b 100644 --- a/@rfSensor/plotPerformance.m +++ b/@rfSensor/plotPerformance.m @@ -9,6 +9,8 @@ function f = plotPerformance(obj, altitude, otherSensorsPos, otherSensors) f (1, 1) {mustBeA(f, "matlab.ui.Figure")}; end + otherSensorsPos = otherSensorsPos + [0, 0, altitude]; + % Create grid on which to evalute SINR, SNR agentPos = [0, 0, altitude]; d = max(10, max(vecnorm(otherSensorsPos(1:2), 2, 2)) * 1.25); @@ -22,20 +24,25 @@ function f = plotPerformance(obj, altitude, otherSensorsPos, otherSensors) SINR = reshape(SINR, size(targetPosX)); SNR = reshape(SNR, size(targetPosX)); - % normalize - SINR = SINR ./ max(SINR); - SNR = SNR ./ max(SNR); + % normalize in linear scale + SINR = 10.^(SINR/10); SINR = SINR ./ max(SINR(:)); SINR = 10 * log10(SINR); + SNR = 10.^(SNR/10); SNR = SNR ./ max(SNR(:)); SNR = 10 * log10(SNR); f = figure; - imagesc(SNR); - axis("image"); - colorbar; - title("Normalized SNR"); + tiledlayout(1, 2, TileSpacing="compact", Padding="compact"); - f = figure; - imagesc(SINR); - axis("image"); + nexttile; + imagesc(distances, distances, SNR); + axis("image"); set(gca, 'YDir', 'normal'); colorbar; - title("Normalized SINR"); + xlabel("X (m)"); ylabel("Y (m)"); + title("Linearly Normalized SNR (dB)"); + + nexttile; + imagesc(distances, distances, SINR); + axis("image"); set(gca, 'YDir', 'normal'); + colorbar; + xlabel("X (m)"); ylabel("Y (m)"); + title("Linearly Normalized SINR (dB)"); end \ No newline at end of file diff --git a/test/test_rfSensor.m b/test/test_rfSensor.m index 5f92eb3..ac2e76c 100644 --- a/test/test_rfSensor.m +++ b/test/test_rfSensor.m @@ -46,7 +46,7 @@ classdef test_rfSensor < matlab.unittest.TestCase tc.testClass = tc.testClass.initialize(P_TX, BW, f_c, G_RX_dBi); altitude = 30; - otherSensorsPos = [5, 0, 400]; % relative to main sensor + otherSensorsPos = [6, -4, -1]; % relative to main sensor otherSensors = cell(1, 1); otherSensors{1} = tc.testClass; % 2 identical sensors From 57a89d93d5c69d09e99e65e0e7a832a1465e0c3b Mon Sep 17 00:00:00 2001 From: Kevin D Date: Sun, 26 Apr 2026 11:58:34 -0700 Subject: [PATCH 11/32] use same frequencies and bandwidths for interferers --- @rfSensor/sensorPerformance.m | 2 ++ test/test_rfSensor.m | 25 ++++++++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/@rfSensor/sensorPerformance.m b/@rfSensor/sensorPerformance.m index 53f83e2..c11ab87 100644 --- a/@rfSensor/sensorPerformance.m +++ b/@rfSensor/sensorPerformance.m @@ -15,6 +15,8 @@ function [SINR, SNR] = sensorPerformance(obj, agentPos, targetPos, otherSensorsP [d, t, a] = obj.computePointToPoints(agentPos, targetPos); % Performance is measured as SINR for this sensor + %% TODO: how should interference calculation be modified for + % interference sources with different center frequencies and bandwidths? S = 10 .^ (0.1 .* obj.RSS(d, t, a)); % Signal I = zeros(size(d)); % Interference from other agents for ii = 1:size(otherSensors, 1) diff --git a/test/test_rfSensor.m b/test/test_rfSensor.m index ac2e76c..b939aed 100644 --- a/test/test_rfSensor.m +++ b/test/test_rfSensor.m @@ -48,7 +48,30 @@ classdef test_rfSensor < matlab.unittest.TestCase altitude = 30; otherSensorsPos = [6, -4, -1]; % relative to main sensor otherSensors = cell(1, 1); - otherSensors{1} = tc.testClass; % 2 identical sensors + otherSensors{1} = tc.testClass; % One interfering sensor, identical to the main sensor + + tc.testClass.plotPerformance(altitude, otherSensorsPos, otherSensors); + + end + + function plot_SINR_heterogenous_interferers(tc) + % Plot sensor performance with no sources of interference + P_TX = 1e-3; % Transmit power (Watts) + BW = 20e6; % Bandwidth (Hz) + f_c = 2e9; % Center frequency (Hz) + G_RX_dBi = 3; % Receiving Antenna Gain (dBi) + + tc.testClass = tc.testClass.initialize(P_TX, BW, f_c, G_RX_dBi); + + altitude = 30; + otherSensorsPos = [6, -4, -1; -2, 6, 0]; % relative to main sensor + otherSensors = cell(2, 1); + otherSensors{1} = rfSensor; % two heterogenous interfering sensors + 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); + otherSensors{2} = otherSensors{2}.initialize(100 * P_TX, BW, f_c, G_RX_dBi); tc.testClass.plotPerformance(altitude, otherSensorsPos, otherSensors); From a202164875014a354c43eef4ce03e4dff6ecd924 Mon Sep 17 00:00:00 2001 From: Kevin D Date: Tue, 28 Apr 2026 21:08:03 -0700 Subject: [PATCH 12/32] rf sensor parameter and performance plotting improvements --- @rfSensor/computePointToPoints.m | 2 +- @rfSensor/plotParameters.m | 23 ++++++++++++----------- @rfSensor/plotPerformance.m | 8 ++++++-- @rfSensor/transmitterGain.m | 9 +++++++-- test/test_rfSensor.m | 2 -- 5 files changed, 26 insertions(+), 18 deletions(-) diff --git a/@rfSensor/computePointToPoints.m b/@rfSensor/computePointToPoints.m index fcf36ca..529a656 100644 --- a/@rfSensor/computePointToPoints.m +++ b/@rfSensor/computePointToPoints.m @@ -16,7 +16,7 @@ function [d, t, a] = computePointToPoints(obj, agentPos, targetPos) % distance from sensor nadir to target nadir (i.e. distance ignoring altitude) x = vecnorm(agentPos(1:2) - targetPos(:, 1:2), 2, 2); - % tilt angle (degrees) (-90, 0 (down), 90) + % tilt angle (degrees) (0 (nadir), 180 (zenith)) t = (180 - atan2d(x, targetPos(:, 3) - agentPos(3))); % azimuth angle (degrees) (0 (+y) clockwise to 360) diff --git a/@rfSensor/plotParameters.m b/@rfSensor/plotParameters.m index e4c5778..f428f3f 100644 --- a/@rfSensor/plotParameters.m +++ b/@rfSensor/plotParameters.m @@ -7,9 +7,9 @@ function f = plotParameters(obj) end % Distance and tilt sample points - d_values = [0.1, 0.5, 1:1:9, 10:2:19, 20:5:49, 50:10:100]; - t_values = -90:0.5:90; - a_values = 0:0.5:360; + d_values = 10.^[1, 2, 3, 4, 5, 6]; + t_values = 0:2.5:180; % 0=nadir (center), 180=zenith (edge) + a_values = 0:2.5:360; % Make grid of values of distances and tilts [d_mg, t_mg, a_mg] = meshgrid(d_values, t_values, a_values); @@ -20,17 +20,18 @@ function f = plotParameters(obj) s_x = reshape(s_x, size(d_mg)); [T, A] = meshgrid(t_values, a_values); % Naz x Nel - Tr = deg2rad(T); Ar = deg2rad(A); - - figure; + + f = figure; hold("on"); for ii = 1:numel(d_values) - % geometry (your "tilt from nadir, stack by distance") - X = d_values(ii) * cos(Ar) .* sin(Tr); - Y = d_values(ii) * sin(Ar) .* sin(Tr); - Z = d_values(ii) * ones(size(X)); + % Linear radial mapping: t=0 (nadir) -> center, t=180 (zenith) -> edge + % Radius in log10 units to match Z axis scale + r = log10(d_values(ii)) .* T ./ 180; + X = r .* cos(Ar); + Y = r .* sin(Ar); + Z = log10(d_values(ii)) * ones(size(X)); % evaluate or extract this slice Fslice = squeeze(s_x(:, ii, :))'; @@ -45,7 +46,7 @@ function f = plotParameters(obj) colormap(turbo); colorbar; daspect([1 1 0.2]) % Separate Z further for more distinct layers - xlabel('X'); ylabel('Y'); zlabel('Distance (m)'); + xlabel('X (log_{10} units)'); ylabel('Y (log_{10} units)'); zlabel('log_{10} Distance (m)'); set(gca,'ZDir','reverse'); view(3); axis("vis3d"); diff --git a/@rfSensor/plotPerformance.m b/@rfSensor/plotPerformance.m index f44b77b..28a780d 100644 --- a/@rfSensor/plotPerformance.m +++ b/@rfSensor/plotPerformance.m @@ -13,7 +13,10 @@ function f = plotPerformance(obj, altitude, otherSensorsPos, otherSensors) % Create grid on which to evalute SINR, SNR agentPos = [0, 0, altitude]; - d = max(10, max(vecnorm(otherSensorsPos(1:2), 2, 2)) * 1.25); + d = 10; + if ~isempty(otherSensorsPos) + d = max(d, max(vecnorm(otherSensorsPos(:, 1:2), 2, 2)) * 1.25); + end c = 0.1; d = ceil(d / c) * c; distances = -d:c:d; @@ -37,6 +40,7 @@ function f = plotPerformance(obj, altitude, otherSensorsPos, otherSensors) colorbar; xlabel("X (m)"); ylabel("Y (m)"); title("Linearly Normalized SNR (dB)"); + subtitle("No interfering sources"); nexttile; imagesc(distances, distances, SINR); @@ -44,5 +48,5 @@ function f = plotPerformance(obj, altitude, otherSensorsPos, otherSensors) colorbar; xlabel("X (m)"); ylabel("Y (m)"); title("Linearly Normalized SINR (dB)"); - + subtitle(sprintf("%d interfering source(s)", size(otherSensorsPos, 1))); end \ No newline at end of file diff --git a/@rfSensor/transmitterGain.m b/@rfSensor/transmitterGain.m index 2b7a0c6..b260380 100644 --- a/@rfSensor/transmitterGain.m +++ b/@rfSensor/transmitterGain.m @@ -11,6 +11,11 @@ function value = transmitterGain(obj, t, a) error("t and a must be the same size"); end - % Temporary logic to make nadir-pointing most effective - value = 10 .* log10(cosd(t) .^ 2) + 10 .* log10((0.5 + 0.5 .* cosd(a)) .^ 4); + n_t = 4; % tilt beamwidth (higher = narrower beam) + n_a = 4; % azimuth rolloff sharpness (higher = more directional) + + % Elevation: cardioid family, null at zenith (t=180°), peak at nadir (t=0°) + % Azimuth: cardioid family, peak at a=0° (+y), null at a=180° (-y) + value = 10 .* n_t .* log10((1 + cosd(t)) ./ 2) + ... + 10 .* n_a .* log10((0.5 + 0.5 .* cosd(a))); end diff --git a/test/test_rfSensor.m b/test/test_rfSensor.m index b939aed..86cd3dc 100644 --- a/test/test_rfSensor.m +++ b/test/test_rfSensor.m @@ -51,7 +51,6 @@ classdef test_rfSensor < matlab.unittest.TestCase otherSensors{1} = tc.testClass; % One interfering sensor, identical to the main sensor tc.testClass.plotPerformance(altitude, otherSensorsPos, otherSensors); - end function plot_SINR_heterogenous_interferers(tc) @@ -74,7 +73,6 @@ classdef test_rfSensor < matlab.unittest.TestCase otherSensors{2} = otherSensors{2}.initialize(100 * P_TX, BW, f_c, G_RX_dBi); tc.testClass.plotPerformance(altitude, otherSensorsPos, otherSensors); - end end end \ No newline at end of file From 35702a6ce2fbf2d4c5f27c52663f3fd35c61b77f Mon Sep 17 00:00:00 2001 From: Kevin D Date: Thu, 30 Apr 2026 09:49:16 -0700 Subject: [PATCH 13/32] added antenna pointing parameters --- @rfSensor/initialize.m | 6 +++- @rfSensor/plotParameters.m | 58 +++++++++++++++++-------------------- @rfSensor/rfSensor.m | 2 ++ @rfSensor/transmitterGain.m | 16 ++++++---- test/test_rfSensor.m | 12 ++++---- 5 files changed, 50 insertions(+), 44 deletions(-) diff --git a/@rfSensor/initialize.m b/@rfSensor/initialize.m index 9dfb966..28c584b 100644 --- a/@rfSensor/initialize.m +++ b/@rfSensor/initialize.m @@ -1,10 +1,12 @@ -function obj = initialize(obj, txPower, bandwidth, centerFreq, rxGain_dBi) +function obj = initialize(obj, txPower, bandwidth, centerFreq, rxGain_dBi, boresightTilt, boresightAzimuth) arguments (Input) obj (1, 1) {mustBeA(obj, "rfSensor")} txPower (1, 1) double; bandwidth (1, 1) double; centerFreq (1, 1) double; rxGain_dBi (1, 1) double; + boresightTilt (1, 1) double = 0; + boresightAzimuth (1, 1) double = 0; end arguments (Output) obj (1, 1) {mustBeA(obj, "rfSensor")} @@ -15,6 +17,8 @@ function obj = initialize(obj, txPower, bandwidth, centerFreq, rxGain_dBi) obj.BW = bandwidth; % Bandwidth (Hz) obj.f_c = centerFreq; % Center frequency (Hz) obj.G_RX_dBi = rxGain_dBi; % Receiving Antenna Gain (dBi) + obj.boresightTilt = boresightTilt; + obj.boresightAzimuth = boresightAzimuth; % Computed values obj.P_TX_dBm = 10*log10(obj.P_TX/1e-3); % Transmit power in dBm diff --git a/@rfSensor/plotParameters.m b/@rfSensor/plotParameters.m index f428f3f..002aab9 100644 --- a/@rfSensor/plotParameters.m +++ b/@rfSensor/plotParameters.m @@ -5,52 +5,48 @@ function f = plotParameters(obj) arguments (Output) f (1, 1) {mustBeA(f, "matlab.ui.Figure")}; end - - % Distance and tilt sample points - d_values = 10.^[1, 2, 3, 4, 5, 6]; - t_values = 0:2.5:180; % 0=nadir (center), 180=zenith (edge) + + % Agent altitude layers and angle sample points + alt_values = 10.^[1, 2, 3, 4]; + t_values = 0:2.5:87.5; % 0=nadir (center), <90=near horizon (edge) a_values = 0:2.5:360; - % Make grid of values of distances and tilts - [d_mg, t_mg, a_mg] = meshgrid(d_values, t_values, a_values); - d = d_mg(:); t = t_mg(:); a = a_mg(:); % flatten - - % Sample received signal strength (no interference or noise) - s_x = obj.RSS(d, t, a); - s_x = reshape(s_x, size(d_mg)); - [T, A] = meshgrid(t_values, a_values); % Naz x Nel Ar = deg2rad(A); f = figure; hold("on"); - for ii = 1:numel(d_values) - % Linear radial mapping: t=0 (nadir) -> center, t=180 (zenith) -> edge - % Radius in log10 units to match Z axis scale - r = log10(d_values(ii)) .* T ./ 180; + for ii = 1:numel(alt_values) + alt = alt_values(ii); + + % 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(:)); + Fslice = reshape(rss, size(D)); + + % Disc geometry: t=0 (nadir) -> center, t~90 (horizon) -> edge + r = log10(alt) .* T ./ 90; X = r .* cos(Ar); Y = r .* sin(Ar); - Z = log10(d_values(ii)) * ones(size(X)); - - % evaluate or extract this slice - Fslice = squeeze(s_x(:, ii, :))'; - - % plot as its own surface - h = surf(X, Y, Z, Fslice); - h.EdgeColor = 'none'; - h.FaceColor = 'interp'; - h.FaceAlpha = 0.25; + Z = log10(alt) * ones(size(X)); + + hs = surf(X, Y, Z, Fslice); + hs.EdgeColor = 'none'; + hs.FaceColor = 'interp'; + hs.FaceAlpha = 0.25; end colormap(turbo); colorbar; - daspect([1 1 0.2]) % Separate Z further for more distinct layers - xlabel('X (log_{10} units)'); ylabel('Y (log_{10} units)'); zlabel('log_{10} Distance (m)'); - set(gca,'ZDir','reverse'); - view(3); + daspect([1 1 0.2]); + xlabel('X (log_{10} units)'); ylabel('Y (log_{10} units)'); zlabel('log_{10} Altitude (m)'); + set(gca, 'ZDir', 'reverse'); + view(3); axis("vis3d"); grid("on"); scatter3(0, 0, 0, 'rx'); hold("off"); -end \ No newline at end of file +end diff --git a/@rfSensor/rfSensor.m b/@rfSensor/rfSensor.m index 20ac328..47ccb93 100644 --- a/@rfSensor/rfSensor.m +++ b/@rfSensor/rfSensor.m @@ -9,6 +9,8 @@ classdef rfSensor BW = NaN; % Bandwidth (Hz) f_c = NaN; % Center frequency (Hz) G_RX_dBi = NaN; % Receiver antenna gain + boresightTilt = 0; % Antenna boresight tilt (deg): 0=nadir, 90=horizon + boresightAzimuth = 0; % Antenna boresight azimuth (deg): 0=+y, 90=+x % Values computed at initialization P_TX_dBm = NaN; % Transmit power (dBm) N = NaN; % Thermal noise diff --git a/@rfSensor/transmitterGain.m b/@rfSensor/transmitterGain.m index b260380..5fbecc4 100644 --- a/@rfSensor/transmitterGain.m +++ b/@rfSensor/transmitterGain.m @@ -11,11 +11,15 @@ function value = transmitterGain(obj, t, a) error("t and a must be the same size"); end - n_t = 4; % tilt beamwidth (higher = narrower beam) - n_a = 4; % azimuth rolloff sharpness (higher = more directional) + n = 4; % beamwidth exponent (higher = narrower beam) - % Elevation: cardioid family, null at zenith (t=180°), peak at nadir (t=0°) - % Azimuth: cardioid family, peak at a=0° (+y), null at a=180° (-y) - value = 10 .* n_t .* log10((1 + cosd(t)) ./ 2) + ... - 10 .* n_a .* log10((0.5 + 0.5 .* cosd(a))); + % 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.boresightTilt) .* sind(t) .* cosd(a - obj.boresightAzimuth) + ... + cosd(obj.boresightTilt) .* 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 .* n .* log10((1 + cosd(theta)) ./ 2); end diff --git a/test/test_rfSensor.m b/test/test_rfSensor.m index 86cd3dc..2b777b6 100644 --- a/test/test_rfSensor.m +++ b/test/test_rfSensor.m @@ -19,7 +19,7 @@ classdef test_rfSensor < matlab.unittest.TestCase f_c = 2e9; % Center frequency (Hz) G_RX_dBi = 3; % Receiving Antenna Gain (dBi) - tc.testClass = tc.testClass.initialize(P_TX, BW, f_c, G_RX_dBi); + tc.testClass = tc.testClass.initialize(P_TX, BW, f_c, G_RX_dBi, 0, 0); tc.testClass.plotParameters(); end @@ -30,7 +30,7 @@ classdef test_rfSensor < matlab.unittest.TestCase f_c = 2e9; % Center frequency (Hz) G_RX_dBi = 3; % Receiving Antenna Gain (dBi) - tc.testClass = tc.testClass.initialize(P_TX, BW, f_c, G_RX_dBi); + tc.testClass = tc.testClass.initialize(P_TX, BW, f_c, G_RX_dBi, 0, 0); altitude = 30; @@ -43,7 +43,7 @@ classdef test_rfSensor < matlab.unittest.TestCase f_c = 2e9; % Center frequency (Hz) G_RX_dBi = 3; % Receiving Antenna Gain (dBi) - tc.testClass = tc.testClass.initialize(P_TX, BW, f_c, G_RX_dBi); + tc.testClass = tc.testClass.initialize(P_TX, BW, f_c, G_RX_dBi, 0, 0); altitude = 30; otherSensorsPos = [6, -4, -1]; % relative to main sensor @@ -60,7 +60,7 @@ classdef test_rfSensor < matlab.unittest.TestCase f_c = 2e9; % Center frequency (Hz) G_RX_dBi = 3; % Receiving Antenna Gain (dBi) - tc.testClass = tc.testClass.initialize(P_TX, BW, f_c, G_RX_dBi); + tc.testClass = tc.testClass.initialize(P_TX, BW, f_c, G_RX_dBi, 0, 0); altitude = 30; otherSensorsPos = [6, -4, -1; -2, 6, 0]; % relative to main sensor @@ -69,8 +69,8 @@ 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); - otherSensors{2} = otherSensors{2}.initialize(100 * P_TX, BW, f_c, G_RX_dBi); + otherSensors{1} = otherSensors{1}.initialize(10 * P_TX, BW, f_c, G_RX_dBi, 0, 0); + otherSensors{2} = otherSensors{2}.initialize(10 * P_TX, BW, f_c, G_RX_dBi, 0, 0); tc.testClass.plotPerformance(altitude, otherSensorsPos, otherSensors); end From 6349212dd54379545f2313d5e46a62df17d8446f Mon Sep 17 00:00:00 2001 From: Kevin D Date: Thu, 30 Apr 2026 10:25:40 -0700 Subject: [PATCH 14/32] cache RSS data for efficiency in computing all timestep SINRs --- @rfSensor/rfSensor.m | 4 +- @rfSensor/sensorPerformance.m | 24 +++++++----- test/test_rfSensor.m | 69 +++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 10 deletions(-) diff --git a/@rfSensor/rfSensor.m b/@rfSensor/rfSensor.m index 47ccb93..3162029 100644 --- a/@rfSensor/rfSensor.m +++ b/@rfSensor/rfSensor.m @@ -14,11 +14,13 @@ classdef rfSensor % Values computed at initialization P_TX_dBm = NaN; % Transmit power (dBm) N = NaN; % Thermal noise + % Cached state (per timestep) + rssCache (:,1) double = double.empty(0,1); % linear-scale RSS to last target grid end methods (Access = public) [obj] = initialize(obj, txPower, bandwidth, centerFreq, rxGain); % initialize sensor, define parameters - [SINR, SNR] = sensorPerformance(obj, agentPos, targetPos, otherSensorsPos, otherSensors); % determine sensor performance for a given single sensor and target geometry + [SINR, SNR, obj, otherSensors] = sensorPerformance(obj, agentPos, targetPos, otherSensorsPos, otherSensors); % determine sensor performance for a given single sensor and target geometry [d, t, a] = computePointToPoints(obj, agentPos, targetPos); [f] = plotParameters(obj); % debug, plot sensor response as a function of distance and tilt angle [f] = plotPerformance(obj, altitude, otherSensorsPos, otherSensors); % debug, plot SNR or SINR ground heatmap for a given geometry diff --git a/@rfSensor/sensorPerformance.m b/@rfSensor/sensorPerformance.m index c11ab87..1842e21 100644 --- a/@rfSensor/sensorPerformance.m +++ b/@rfSensor/sensorPerformance.m @@ -1,4 +1,4 @@ -function [SINR, SNR] = sensorPerformance(obj, agentPos, targetPos, otherSensorsPos, otherSensors) +function [SINR, SNR, obj, otherSensors] = sensorPerformance(obj, agentPos, targetPos, otherSensorsPos, otherSensors) arguments (Input) obj (1, 1) {mustBeA(obj, "rfSensor")}; agentPos (1, 3) double; @@ -9,21 +9,27 @@ function [SINR, SNR] = sensorPerformance(obj, agentPos, targetPos, otherSensorsP arguments (Output) SINR (:, 1) double; SNR (:, 1) double; + obj (1, 1) {mustBeA(obj, "rfSensor")}; + otherSensors (:, 1) cell; end assert(size(otherSensorsPos, 1) == size(otherSensors, 1), "Mismatch in number of other sensor positions (%d) and number of other sensors (%d) provided", size(otherSensorsPos, 1), size(otherSensors, 1)); [d, t, a] = obj.computePointToPoints(agentPos, targetPos); - % Performance is measured as SINR for this sensor - %% TODO: how should interference calculation be modified for - % interference sources with different center frequencies and bandwidths? - S = 10 .^ (0.1 .* obj.RSS(d, t, a)); % Signal - I = zeros(size(d)); % Interference from other agents + if isempty(obj.rssCache) + obj.rssCache = 10 .^ (0.1 .* obj.RSS(d, t, a)); + end + S = obj.rssCache; + + I = zeros(size(d)); for ii = 1:size(otherSensors, 1) - [d_other, t_other, a_other] = otherSensors{ii}.computePointToPoints(otherSensorsPos(ii, 1:3), targetPos); - I = I + 10 .^ (0.1 .* otherSensors{ii}.RSS(d_other, t_other, a_other)); + if isempty(otherSensors{ii}.rssCache) + [d_other, t_other, a_other] = otherSensors{ii}.computePointToPoints(otherSensorsPos(ii, 1:3), targetPos); + otherSensors{ii}.rssCache = 10 .^ (0.1 .* otherSensors{ii}.RSS(d_other, t_other, a_other)); + end + I = I + otherSensors{ii}.rssCache; end SINR = 10*log10(S ./ (I + obj.N)); SNR = 10*log10(S ./ obj.N); -end \ No newline at end of file +end diff --git a/test/test_rfSensor.m b/test/test_rfSensor.m index 2b777b6..3a9e4c8 100644 --- a/test/test_rfSensor.m +++ b/test/test_rfSensor.m @@ -74,5 +74,74 @@ classdef test_rfSensor < matlab.unittest.TestCase tc.testClass.plotPerformance(altitude, otherSensorsPos, otherSensors); end + + function plot_SINR_heterogenous_interferers_efficiently(tc) + P_TX = 1e-3; + BW = 20e6; + f_c = 2e9; + G_RX_dBi = 3; + altitude = 30; + + sensor1 = rfSensor; + sensor1 = sensor1.initialize(P_TX, BW, f_c, G_RX_dBi, 0, 0); + sensor2 = rfSensor; + sensor2 = sensor2.initialize(P_TX, BW, f_c, G_RX_dBi, 0, 0); + sensor3 = rfSensor; + sensor3 = sensor3.initialize(P_TX, BW, f_c, G_RX_dBi, 0, 0); + + pos1 = [0, 0, altitude]; + pos2 = [6, -4, altitude - 1]; + pos3 = [-2, 6, altitude]; + + % Build a shared target grid + distances = -15:0.25:15; + [Xg, Yg] = meshgrid(distances, distances); + targetPos = [Xg(:), Yg(:), zeros(numel(Xg), 1)]; + + % Call 1: cache empty, does all computations for this timestep + [SINR1, ~, sensor1, others] = sensor1.sensorPerformance(pos1, targetPos, [pos2; pos3], {sensor2; sensor3}); + sensor2 = others{1}; + sensor3 = others{2}; + + % Calls 2 and 3 use cached data + [SINR2, ~, sensor2, others] = sensor2.sensorPerformance(pos2, targetPos, [pos1; pos3], {sensor1; sensor3}); + sensor1 = others{1}; + sensor3 = others{2}; + + [SINR3, ~, sensor3, ~] = sensor3.sensorPerformance(pos3, targetPos, [pos1; pos2], {sensor1; sensor2}); + + + % All caches should be populated after the three calls + tc.assertNotEmpty(sensor1.rssCache); + tc.assertNotEmpty(sensor2.rssCache); + tc.assertNotEmpty(sensor3.rssCache); + + % Plot SINR from each UAV's perspective + sz = size(Xg); + SINR1 = reshape(SINR1, sz); + SINR2 = reshape(SINR2, sz); + SINR3 = reshape(SINR3, sz); + + f = figure; + tiledlayout(f, 1, 3, TileSpacing="compact", Padding="compact"); + + nexttile; + imagesc(distances, distances, SINR1); axis image; set(gca, YDir="normal"); hold on; + scatter(pos1(1), pos1(2), 80, "g", "o", LineWidth=2); + scatter([pos2(1), pos3(1)], [pos2(2), pos3(2)], 80, "r", "x", LineWidth=2); + hold off; cb = colorbar; cb.Label.String = "SINR (dB)"; xlabel("X (m)"); ylabel("Y (m)"); title("SINR: UAV 1"); + + nexttile; + imagesc(distances, distances, SINR2); axis image; set(gca, YDir="normal"); hold on; + scatter(pos2(1), pos2(2), 80, "g", "o", LineWidth=2); + scatter([pos1(1), pos3(1)], [pos1(2), pos3(2)], 80, "r", "x", LineWidth=2); + hold off; cb = colorbar; cb.Label.String = "SINR (dB)"; xlabel("X (m)"); ylabel("Y (m)"); title("SINR: UAV 2"); + + nexttile; + imagesc(distances, distances, SINR3); axis image; set(gca, YDir="normal"); hold on; + scatter(pos3(1), pos3(2), 80, "g", "o", LineWidth=2); + scatter([pos1(1), pos2(1)], [pos1(2), pos2(2)], 80, "r", "x", LineWidth=2); + hold off; cb = colorbar; cb.Label.String = "SINR (dB)"; xlabel("X (m)"); ylabel("Y (m)"); title("SINR: UAV 3"); + end end end \ No newline at end of file From 81fe1b67c5eb7c584363dd4a360638ce075d6eee Mon Sep 17 00:00:00 2001 From: Kevin D Date: Sun, 3 May 2026 08:40:36 -0700 Subject: [PATCH 15/32] renamed antenna angle states --- @rfSensor/initialize.m | 10 +++++----- @rfSensor/rfSensor.m | 4 ++-- @rfSensor/transmitterGain.m | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/@rfSensor/initialize.m b/@rfSensor/initialize.m index 28c584b..14db6f3 100644 --- a/@rfSensor/initialize.m +++ b/@rfSensor/initialize.m @@ -1,12 +1,12 @@ -function obj = initialize(obj, txPower, bandwidth, centerFreq, rxGain_dBi, boresightTilt, boresightAzimuth) +function obj = initialize(obj, txPower, bandwidth, centerFreq, rxGain_dBi, tilt, azimuth) arguments (Input) obj (1, 1) {mustBeA(obj, "rfSensor")} txPower (1, 1) double; bandwidth (1, 1) double; centerFreq (1, 1) double; rxGain_dBi (1, 1) double; - boresightTilt (1, 1) double = 0; - boresightAzimuth (1, 1) double = 0; + tilt (1, 1) double = 0; + azimuth (1, 1) double = 0; end arguments (Output) obj (1, 1) {mustBeA(obj, "rfSensor")} @@ -17,8 +17,8 @@ function obj = initialize(obj, txPower, bandwidth, centerFreq, rxGain_dBi, bores obj.BW = bandwidth; % Bandwidth (Hz) obj.f_c = centerFreq; % Center frequency (Hz) obj.G_RX_dBi = rxGain_dBi; % Receiving Antenna Gain (dBi) - obj.boresightTilt = boresightTilt; - obj.boresightAzimuth = boresightAzimuth; + obj.tilt = tilt; + obj.azimuth = azimuth; % Computed values obj.P_TX_dBm = 10*log10(obj.P_TX/1e-3); % Transmit power in dBm diff --git a/@rfSensor/rfSensor.m b/@rfSensor/rfSensor.m index 3162029..8b125c0 100644 --- a/@rfSensor/rfSensor.m +++ b/@rfSensor/rfSensor.m @@ -9,8 +9,8 @@ classdef rfSensor BW = NaN; % Bandwidth (Hz) f_c = NaN; % Center frequency (Hz) G_RX_dBi = NaN; % Receiver antenna gain - boresightTilt = 0; % Antenna boresight tilt (deg): 0=nadir, 90=horizon - boresightAzimuth = 0; % Antenna boresight azimuth (deg): 0=+y, 90=+x + tilt = NaN; % Antenna boresight tilt (deg): 0=nadir, 90=horizon + azimuth = NaN; % Antenna boresight azimuth (deg): 0=+y, 90=+x, 180=-y, 270=-x % Values computed at initialization P_TX_dBm = NaN; % Transmit power (dBm) N = NaN; % Thermal noise diff --git a/@rfSensor/transmitterGain.m b/@rfSensor/transmitterGain.m index 5fbecc4..35c3c58 100644 --- a/@rfSensor/transmitterGain.m +++ b/@rfSensor/transmitterGain.m @@ -15,8 +15,8 @@ function value = transmitterGain(obj, t, a) % 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.boresightTilt) .* sind(t) .* cosd(a - obj.boresightAzimuth) + ... - cosd(obj.boresightTilt) .* cosd(t); + cos_theta = sind(obj.tilt) .* sind(t) .* cosd(a - obj.tilt) + ... + cosd(obj.tilt) .* cosd(t); cos_theta = max(-1, min(1, cos_theta)); % clamp for numerical safety theta = acosd(cos_theta); From 4159a3a5cbd9a2a8fedb27dc290cf7ee9a29a095 Mon Sep 17 00:00:00 2001 From: Kevin D Date: Sun, 3 May 2026 09:23:21 -0700 Subject: [PATCH 16/32] coordinate bug cleanup --- @rfSensor/clearRssCache.m | 11 +++++++++++ @rfSensor/rfSensor.m | 1 + @rfSensor/transmitterGain.m | 2 +- test/test_rfSensor.m | 10 +++++++++- 4 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 @rfSensor/clearRssCache.m diff --git a/@rfSensor/clearRssCache.m b/@rfSensor/clearRssCache.m new file mode 100644 index 0000000..29a772b --- /dev/null +++ b/@rfSensor/clearRssCache.m @@ -0,0 +1,11 @@ +function obj = clearRssCache(obj) + arguments (Input) + obj (1, 1) {mustBeA(obj, "rfSensor")}; + end + arguments (Output) + obj (1, 1) {mustBeA(obj, "rfSensor")}; + end + + obj.rssCache = double.empty(0, 1); + +end \ No newline at end of file diff --git a/@rfSensor/rfSensor.m b/@rfSensor/rfSensor.m index 8b125c0..9f1a271 100644 --- a/@rfSensor/rfSensor.m +++ b/@rfSensor/rfSensor.m @@ -24,6 +24,7 @@ classdef rfSensor [d, t, a] = computePointToPoints(obj, agentPos, targetPos); [f] = plotParameters(obj); % debug, plot sensor response as a function of distance and tilt angle [f] = plotPerformance(obj, altitude, otherSensorsPos, otherSensors); % debug, plot SNR or SINR ground heatmap for a given geometry + obj = clearRssCache(obj); end methods (Access = private) x = RSS(obj, d, t, a); % Received signal strength (function of distance and tilt angle) diff --git a/@rfSensor/transmitterGain.m b/@rfSensor/transmitterGain.m index 35c3c58..c85941b 100644 --- a/@rfSensor/transmitterGain.m +++ b/@rfSensor/transmitterGain.m @@ -15,7 +15,7 @@ function value = transmitterGain(obj, t, a) % 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.tilt) + ... + 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); diff --git a/test/test_rfSensor.m b/test/test_rfSensor.m index 3a9e4c8..3bf6b84 100644 --- a/test/test_rfSensor.m +++ b/test/test_rfSensor.m @@ -30,10 +30,18 @@ classdef test_rfSensor < matlab.unittest.TestCase f_c = 2e9; % Center frequency (Hz) G_RX_dBi = 3; % Receiving Antenna Gain (dBi) - tc.testClass = tc.testClass.initialize(P_TX, BW, f_c, G_RX_dBi, 0, 0); + tc.testClass = tc.testClass.initialize(P_TX, BW, f_c, G_RX_dBi, 30, 135); altitude = 30; + % 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). + agentPos = [0, 0, altitude]; + [~, snrA] = tc.testClass.sensorPerformance(agentPos, [5, -5, 0]); + % tc.testClass = tc.testClass.clearRssCache(); + [~, snrB] = tc.testClass.sensorPerformance(agentPos, [5, 5, 0]); + tc.assertGreaterThan(snrA, snrB, "SNR should be higher toward boresight (+X,-Y) than away from it (+X,+Y)"); + tc.testClass.plotPerformance(altitude); end function plot_SINR_one_interferer(tc) From 0490dd656da98418fb384bd44f25ddf71f474bd5 Mon Sep 17 00:00:00 2001 From: Kevin D Date: Sun, 3 May 2026 10:53:14 -0700 Subject: [PATCH 17/32] added antenna LOS pointing to diagnostic plots --- @rfSensor/plotPerformance.m | 52 ++++++++++++++++++++++++++++++++----- test/test_rfSensor.m | 45 +++++++++----------------------- 2 files changed, 57 insertions(+), 40 deletions(-) diff --git a/@rfSensor/plotPerformance.m b/@rfSensor/plotPerformance.m index 28a780d..df9dfe9 100644 --- a/@rfSensor/plotPerformance.m +++ b/@rfSensor/plotPerformance.m @@ -9,6 +9,12 @@ function f = plotPerformance(obj, altitude, otherSensorsPos, otherSensors) f (1, 1) {mustBeA(f, "matlab.ui.Figure")}; end + % Clear local caches so this visualization always uses its own grid + obj.rssCache = []; + for ii = 1:numel(otherSensors) + otherSensors{ii}.rssCache = []; + end + otherSensorsPos = otherSensorsPos + [0, 0, altitude]; % Create grid on which to evalute SINR, SNR @@ -29,24 +35,56 @@ function f = plotPerformance(obj, altitude, otherSensorsPos, otherSensors) % normalize in linear scale SINR = 10.^(SINR/10); SINR = SINR ./ max(SINR(:)); SINR = 10 * log10(SINR); - SNR = 10.^(SNR/10); SNR = SNR ./ max(SNR(:)); SNR = 10 * log10(SNR); - + SNR = 10.^(SNR/10); SNR = SNR ./ max(SNR(:)); SNR = 10 * log10(SNR); + + % Collect sensor positions and boresight parameters for overlay + sensorXY = [0, 0; otherSensorsPos(:, 1:2)]; + sensorTilts = [obj.tilt; cellfun(@(s) s.tilt, otherSensors)]; + sensorAzimuths = [obj.azimuth; cellfun(@(s) s.azimuth, otherSensors)]; + tailScale = 0.5 * d; + f = figure; tiledlayout(1, 2, TileSpacing="compact", Padding="compact"); nexttile; imagesc(distances, distances, SNR); axis("image"); set(gca, 'YDir', 'normal'); - colorbar; - xlabel("X (m)"); ylabel("Y (m)"); + colorbar; xlabel("X (m)"); ylabel("Y (m)"); title("Linearly Normalized SNR (dB)"); subtitle("No interfering sources"); + addSensorOverlay(gca, sensorXY, sensorTilts, sensorAzimuths, tailScale); nexttile; imagesc(distances, distances, SINR); axis("image"); set(gca, 'YDir', 'normal'); - colorbar; - xlabel("X (m)"); ylabel("Y (m)"); + colorbar; xlabel("X (m)"); ylabel("Y (m)"); title("Linearly Normalized SINR (dB)"); subtitle(sprintf("%d interfering source(s)", size(otherSensorsPos, 1))); -end \ No newline at end of file + addSensorOverlay(gca, sensorXY, sensorTilts, sensorAzimuths, tailScale); +end + +function addSensorOverlay(ax, sensorXY, tilts, azimuths, tailScale) + % Draw a marker + boresight arrow for each sensor. + % Tail direction follows azimuth convention (0=+Y, 90=+X, clockwise). + % Tail length = tailScale * sind(tilt), so nadir (0°) has no tail and + % horizon (90°) has the full tailScale length. + hold(ax, 'on'); + for ii = 1:size(sensorXY, 1) + x = sensorXY(ii, 1); + y = sensorXY(ii, 2); + if ii == 1 + c = [0, 0, 0]; + mk = 'o'; + else + c = [0.9, 0.2, 0.2]; + mk = 'x'; + end + scatter(ax, x, y, 80, c, mk, LineWidth=2); + if tilts(ii) > 0 + u = tailScale * sind(tilts(ii)) * sind(azimuths(ii)); + v = tailScale * sind(tilts(ii)) * cosd(azimuths(ii)); + quiver(ax, x, y, u, v, 0, Color=c, LineWidth=2, MaxHeadSize=1.0); + end + end + hold(ax, 'off'); +end diff --git a/test/test_rfSensor.m b/test/test_rfSensor.m index 3bf6b84..fa7a988 100644 --- a/test/test_rfSensor.m +++ b/test/test_rfSensor.m @@ -91,11 +91,11 @@ classdef test_rfSensor < matlab.unittest.TestCase altitude = 30; sensor1 = rfSensor; - sensor1 = sensor1.initialize(P_TX, BW, f_c, G_RX_dBi, 0, 0); + sensor1 = sensor1.initialize(P_TX, BW, f_c, G_RX_dBi, 15, 45); sensor2 = rfSensor; - sensor2 = sensor2.initialize(P_TX, BW, f_c, G_RX_dBi, 0, 0); + sensor2 = sensor2.initialize(P_TX, BW, f_c, G_RX_dBi, 10, 150); sensor3 = rfSensor; - sensor3 = sensor3.initialize(P_TX, BW, f_c, G_RX_dBi, 0, 0); + sensor3 = sensor3.initialize(P_TX, BW, f_c, G_RX_dBi, 20, 200); pos1 = [0, 0, altitude]; pos2 = [6, -4, altitude - 1]; @@ -107,49 +107,28 @@ classdef test_rfSensor < matlab.unittest.TestCase targetPos = [Xg(:), Yg(:), zeros(numel(Xg), 1)]; % Call 1: cache empty, does all computations for this timestep - [SINR1, ~, sensor1, others] = sensor1.sensorPerformance(pos1, targetPos, [pos2; pos3], {sensor2; sensor3}); + [~, ~, sensor1, others] = sensor1.sensorPerformance(pos1, targetPos, [pos2; pos3], {sensor2; sensor3}); sensor2 = others{1}; sensor3 = others{2}; % Calls 2 and 3 use cached data - [SINR2, ~, sensor2, others] = sensor2.sensorPerformance(pos2, targetPos, [pos1; pos3], {sensor1; sensor3}); + [~, ~, sensor2, others] = sensor2.sensorPerformance(pos2, targetPos, [pos1; pos3], {sensor1; sensor3}); sensor1 = others{1}; sensor3 = others{2}; - [SINR3, ~, sensor3, ~] = sensor3.sensorPerformance(pos3, targetPos, [pos1; pos2], {sensor1; sensor2}); - + [~, ~, sensor3, ~] = sensor3.sensorPerformance(pos3, targetPos, [pos1; pos2], {sensor1; sensor2}); % All caches should be populated after the three calls tc.assertNotEmpty(sensor1.rssCache); tc.assertNotEmpty(sensor2.rssCache); tc.assertNotEmpty(sensor3.rssCache); - % Plot SINR from each UAV's perspective - sz = size(Xg); - SINR1 = reshape(SINR1, sz); - SINR2 = reshape(SINR2, sz); - SINR3 = reshape(SINR3, sz); - - f = figure; - tiledlayout(f, 1, 3, TileSpacing="compact", Padding="compact"); - - nexttile; - imagesc(distances, distances, SINR1); axis image; set(gca, YDir="normal"); hold on; - scatter(pos1(1), pos1(2), 80, "g", "o", LineWidth=2); - scatter([pos2(1), pos3(1)], [pos2(2), pos3(2)], 80, "r", "x", LineWidth=2); - hold off; cb = colorbar; cb.Label.String = "SINR (dB)"; xlabel("X (m)"); ylabel("Y (m)"); title("SINR: UAV 1"); - - nexttile; - imagesc(distances, distances, SINR2); axis image; set(gca, YDir="normal"); hold on; - scatter(pos2(1), pos2(2), 80, "g", "o", LineWidth=2); - scatter([pos1(1), pos3(1)], [pos1(2), pos3(2)], 80, "r", "x", LineWidth=2); - hold off; cb = colorbar; cb.Label.String = "SINR (dB)"; xlabel("X (m)"); ylabel("Y (m)"); title("SINR: UAV 2"); - - nexttile; - imagesc(distances, distances, SINR3); axis image; set(gca, YDir="normal"); hold on; - scatter(pos3(1), pos3(2), 80, "g", "o", LineWidth=2); - scatter([pos1(1), pos2(1)], [pos1(2), pos2(2)], 80, "r", "x", LineWidth=2); - hold off; cb = colorbar; cb.Label.String = "SINR (dB)"; xlabel("X (m)"); ylabel("Y (m)"); title("SINR: UAV 3"); + % Plot SINR from each UAV's perspective. + % otherSensorsPos for plotPerformance: XY = offset from calling sensor, Z = absolute_alt - calling_alt. + % This is exactly posOther - posSelf for each row. + sensor1.plotPerformance(pos1(3), [pos2 - pos1; pos3 - pos1], {sensor2; sensor3}); + sensor2.plotPerformance(pos2(3), [pos1 - pos2; pos3 - pos2], {sensor1; sensor3}); + sensor3.plotPerformance(pos3(3), [pos1 - pos3; pos2 - pos3], {sensor1; sensor2}); end end end \ No newline at end of file From e950d43fc86a849064a5e6d855c12edac1b64c44 Mon Sep 17 00:00:00 2001 From: Kevin D Date: Sun, 3 May 2026 12:04:44 -0700 Subject: [PATCH 18/32] sensor integration cleanup --- @sigmoidSensor/sigmoidSensor.m | 4 ++++ util/objectiveFunctionWrapper.m | 2 +- util/validators/arguments/mustBeSensor.m | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/@sigmoidSensor/sigmoidSensor.m b/@sigmoidSensor/sigmoidSensor.m index 958e9f2..81a23e3 100644 --- a/@sigmoidSensor/sigmoidSensor.m +++ b/@sigmoidSensor/sigmoidSensor.m @@ -5,6 +5,10 @@ classdef sigmoidSensor betaDist = NaN; alphaTilt = NaN; % degrees betaTilt = NaN; + + % pointing parameters + tilt = 0; + azimuth = 0; end methods (Access = public) diff --git a/util/objectiveFunctionWrapper.m b/util/objectiveFunctionWrapper.m index 6846ffe..0a9de95 100644 --- a/util/objectiveFunctionWrapper.m +++ b/util/objectiveFunctionWrapper.m @@ -4,7 +4,7 @@ function f = objectiveFunctionWrapper(center, sigma) % composite objectives in particular arguments (Input) center (:, 2) double; - sigma (:, 2, 2) double = eye(2); + sigma (:, 2, 2) double = reshape(eye(2), 1, 2, 2); end arguments (Output) f (1, 1) {mustBeA(f, "function_handle")}; diff --git a/util/validators/arguments/mustBeSensor.m b/util/validators/arguments/mustBeSensor.m index 6166995..df37919 100644 --- a/util/validators/arguments/mustBeSensor.m +++ b/util/validators/arguments/mustBeSensor.m @@ -1,11 +1,11 @@ function mustBeSensor(sensorModel) if isa(sensorModel, 'cell') for ii = 1:size(sensorModel, 1) - assert(isa(sensorModel{ii}, 'sigmoidSensor'), ... + assert(isa(sensorModel{ii}, 'sigmoidSensor', 'rfSensor'), ... 'Sensor in index %d is not a valid sensor class', ii); end else - assert(isa(sensorModel, 'sigmoidSensor'), ... + assert(isa(sensorModel, 'sigmoidSensor') || isa(sensorModel, 'rfSensor'), ... 'Sensor is not a valid sensor class'); end end \ No newline at end of file From 0e39e0037d72b4b17a68b2c44afef7222391d79f Mon Sep 17 00:00:00 2001 From: Kevin D Date: Sun, 3 May 2026 14:32:53 -0700 Subject: [PATCH 19/32] added sensor tilting and rf sensor sim test cases --- @agent/initialize.m | 2 +- @rfSensor/halfAngle.m | 23 +++++++++++ @rfSensor/rfSensor.m | 1 + @rfSensor/sensorPerformance.m | 4 +- @rfSensor/transmitterGain.m | 2 +- @sigmoidSensor/halfAngle.m | 9 +++++ @sigmoidSensor/initialize.m | 9 ++++- @sigmoidSensor/sensorPerformance.m | 16 +++++--- @sigmoidSensor/sigmoidSensor.m | 3 +- geometries/@cone/cone.m | 8 ++-- geometries/@cone/initialize.m | 28 +++++++------ geometries/@cone/plot.m | 13 +++++- test/test_miSim.m | 65 +++++++++++++++++++++++++++++- 13 files changed, 154 insertions(+), 29 deletions(-) create mode 100644 @rfSensor/halfAngle.m create mode 100644 @sigmoidSensor/halfAngle.m diff --git a/@agent/initialize.m b/@agent/initialize.m index b74a926..47c3e98 100644 --- a/@agent/initialize.m +++ b/@agent/initialize.m @@ -35,5 +35,5 @@ function obj = initialize(obj, pos, collisionGeometry, sensorModel, comRange, ma % Initialize FOV cone obj.fovGeometry = cone; - obj.fovGeometry = obj.fovGeometry.initialize([obj.pos(1:3)], tand(obj.sensorModel.alphaTilt) * obj.pos(3), obj.pos(3), REGION_TYPE.FOV, sprintf("%s FOV", obj.label)); + obj.fovGeometry = obj.fovGeometry.initialize([obj.pos(1:3)], tand(obj.sensorModel.halfAngle()) * obj.pos(3), obj.pos(3), REGION_TYPE.FOV, sprintf("%s FOV", obj.label), obj.sensorModel.tilt, obj.sensorModel.azimuth); end diff --git a/@rfSensor/halfAngle.m b/@rfSensor/halfAngle.m new file mode 100644 index 0000000..53fe95d --- /dev/null +++ b/@rfSensor/halfAngle.m @@ -0,0 +1,23 @@ +function value = halfAngle(obj) + arguments (Input) + obj (1, 1) {mustBeA(obj, "rfSensor")}; + end + 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))); + target = gain(1) - 3; + idx = find(gain <= target, 1); + if isempty(idx) || idx == 1 + value = dtheta(end); + return; + end + % Linear interpolation between bracketing samples + value = dtheta(idx-1) + (target - gain(idx-1)) * ... + (dtheta(idx) - dtheta(idx-1)) / (gain(idx) - gain(idx-1)); +end diff --git a/@rfSensor/rfSensor.m b/@rfSensor/rfSensor.m index 9f1a271..07f7841 100644 --- a/@rfSensor/rfSensor.m +++ b/@rfSensor/rfSensor.m @@ -22,6 +22,7 @@ classdef rfSensor [obj] = initialize(obj, txPower, bandwidth, centerFreq, rxGain); % 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, t, a] = computePointToPoints(obj, agentPos, targetPos); + [value] = halfAngle(obj); % tilt angle (deg) at which sensor performance is halved [f] = plotParameters(obj); % debug, plot sensor response as a function of distance and tilt angle [f] = plotPerformance(obj, altitude, otherSensorsPos, otherSensors); % debug, plot SNR or SINR ground heatmap for a given geometry obj = clearRssCache(obj); diff --git a/@rfSensor/sensorPerformance.m b/@rfSensor/sensorPerformance.m index 1842e21..65783f4 100644 --- a/@rfSensor/sensorPerformance.m +++ b/@rfSensor/sensorPerformance.m @@ -17,7 +17,7 @@ function [SINR, SNR, obj, otherSensors] = sensorPerformance(obj, agentPos, targe [d, t, a] = obj.computePointToPoints(agentPos, targetPos); if isempty(obj.rssCache) - obj.rssCache = 10 .^ (0.1 .* obj.RSS(d, t, a)); + obj.rssCache = 1e-3 .* 10 .^ (0.1 .* obj.RSS(d, t, a)); % dBm → W end S = obj.rssCache; @@ -25,7 +25,7 @@ function [SINR, SNR, obj, otherSensors] = sensorPerformance(obj, agentPos, targe for ii = 1:size(otherSensors, 1) if isempty(otherSensors{ii}.rssCache) [d_other, t_other, a_other] = otherSensors{ii}.computePointToPoints(otherSensorsPos(ii, 1:3), targetPos); - otherSensors{ii}.rssCache = 10 .^ (0.1 .* otherSensors{ii}.RSS(d_other, t_other, a_other)); + otherSensors{ii}.rssCache = 1e-3 .* 10 .^ (0.1 .* otherSensors{ii}.RSS(d_other, t_other, a_other)); % dBm → W end I = I + otherSensors{ii}.rssCache; end diff --git a/@rfSensor/transmitterGain.m b/@rfSensor/transmitterGain.m index c85941b..06e0d68 100644 --- a/@rfSensor/transmitterGain.m +++ b/@rfSensor/transmitterGain.m @@ -11,7 +11,7 @@ function value = transmitterGain(obj, t, a) error("t and a must be the same size"); end - n = 4; % beamwidth exponent (higher = narrower beam) + n = 6; % beamwidth exponent (higher = narrower beam) % Angular offset from boresight via spherical law of cosines % Convention: t=0° nadir, t=90° horizon; a=0° +y, a=90° +x diff --git a/@sigmoidSensor/halfAngle.m b/@sigmoidSensor/halfAngle.m new file mode 100644 index 0000000..8c8ac79 --- /dev/null +++ b/@sigmoidSensor/halfAngle.m @@ -0,0 +1,9 @@ +function value = halfAngle(obj) + arguments (Input) + obj (1, 1) {mustBeA(obj, "sigmoidSensor")}; + end + arguments (Output) + value (1, 1) double; + end + value = obj.alphaTilt; +end diff --git a/@sigmoidSensor/initialize.m b/@sigmoidSensor/initialize.m index 5c8249e..160f034 100644 --- a/@sigmoidSensor/initialize.m +++ b/@sigmoidSensor/initialize.m @@ -1,17 +1,24 @@ -function obj = initialize(obj, alphaDist, betaDist, alphaTilt, betaTilt) +function obj = initialize(obj, alphaDist, betaDist, alphaTilt, betaTilt, tilt, azimuth) arguments (Input) obj (1, 1) {mustBeA(obj, "sigmoidSensor")} alphaDist (1, 1) double; betaDist (1, 1) double; alphaTilt (1, 1) double; betaTilt (1, 1) double; + tilt (1, 1) double = 0; + azimuth (1, 1) double = 0; end arguments (Output) obj (1, 1) {mustBeA(obj, "sigmoidSensor")} end + % Sensor performance parameters obj.alphaDist = alphaDist; obj.betaDist = betaDist; obj.alphaTilt = alphaTilt; obj.betaTilt = betaTilt; + + % Sensor pointing parameters + obj.tilt = tilt; + obj.azimuth = azimuth; end \ No newline at end of file diff --git a/@sigmoidSensor/sensorPerformance.m b/@sigmoidSensor/sensorPerformance.m index 18f1c74..15521c7 100644 --- a/@sigmoidSensor/sensorPerformance.m +++ b/@sigmoidSensor/sensorPerformance.m @@ -8,16 +8,20 @@ function value = sensorPerformance(obj, agentPos, targetPos) value (:, 1) double; end - % compute direct distance and distance projected onto the ground - d = vecnorm(agentPos - targetPos, 2, 2); % distance from sensor to target - x = vecnorm(agentPos(1:2) - targetPos(:, 1:2), 2, 2); % distance from sensor nadir to target nadir (i.e. distance ignoring height difference) + % Unit vectors from agent to each target + diffs = targetPos - agentPos; + d = vecnorm(diffs, 2, 2); + dirs = diffs ./ d; - % compute tilt angle - tiltAngle = (180 - atan2d(x, targetPos(:, 3) - agentPos(3))); % degrees + % Boresight unit vector: tilt=0 → nadir [0,0,-1]; azimuth 0=+Y, 90=+X clockwise + boresight = [sind(obj.tilt)*sind(obj.azimuth), sind(obj.tilt)*cosd(obj.azimuth), -cosd(obj.tilt)]; + + % Angular offset from boresight to each target direction + angularOffset = acosd(dirs * boresight'); % Membership functions mu_d = obj.distanceMembership(d); - mu_t = obj.tiltMembership(tiltAngle); + mu_t = obj.tiltMembership(angularOffset); value = mu_d .* mu_t; % assume pan membership is always 1 end \ No newline at end of file diff --git a/@sigmoidSensor/sigmoidSensor.m b/@sigmoidSensor/sigmoidSensor.m index 81a23e3..8c7879b 100644 --- a/@sigmoidSensor/sigmoidSensor.m +++ b/@sigmoidSensor/sigmoidSensor.m @@ -12,8 +12,9 @@ classdef sigmoidSensor end methods (Access = public) - [obj] = initialize(obj, alphaDist, betaDist, alphaTilt, betaTilt); % initialize sensor, define parameters + [obj] = initialize(obj, alphaDist, betaDist, alphaTilt, betaTilt, tilt, azimuth); % initialize sensor, define parameters [value] = sensorPerformance(obj, agentPos, targetPos); % determine sensor performance for a given single sensor and target geometry + [value] = halfAngle(obj); % tilt angle (deg) at which sensor performance is halved [f] = plotParameters(obj); % debug, plot sensor response as a function of distance and tilt angle end methods (Access = private) diff --git a/geometries/@cone/cone.m b/geometries/@cone/cone.m index c73479b..3f05456 100644 --- a/geometries/@cone/cone.m +++ b/geometries/@cone/cone.m @@ -6,9 +6,11 @@ classdef cone label = ""; % Spatial - center = NaN; - radius = NaN; - height = NaN; + center = NaN; + radius = NaN; + height = NaN; + tilt = 0; % degrees, 0=nadir 90=horizon + azimuth = 0; % degrees, 0=+Y 90=+X clockwise % Plotting surface; diff --git a/geometries/@cone/initialize.m b/geometries/@cone/initialize.m index a811633..736f931 100644 --- a/geometries/@cone/initialize.m +++ b/geometries/@cone/initialize.m @@ -1,19 +1,23 @@ -function obj = initialize(obj, center, radius, height, tag, label) +function obj = initialize(obj, center, radius, height, tag, label, tilt, azimuth) arguments (Input) - obj (1, 1) {mustBeA(obj, "cone")}; - center (1, 3) double; - radius (1, 1) double; - height (1, 1) double; - tag (1, 1) REGION_TYPE = REGION_TYPE.INVALID; - label (1, 1) string = ""; + obj (1, 1) {mustBeA(obj, "cone")}; + center (1, 3) double; + radius (1, 1) double; + height (1, 1) double; + tag (1, 1) REGION_TYPE = REGION_TYPE.INVALID; + label (1, 1) string = ""; + tilt (1, 1) double = 0; + azimuth (1, 1) double = 0; end arguments (Output) obj (1, 1) {mustBeA(obj, "cone")}; end - obj.center = center; - obj.radius = radius; - obj.height = height; - obj.tag = tag; - obj.label = label; + obj.center = center; + obj.radius = radius; + obj.height = height; + obj.tag = tag; + obj.label = label; + obj.tilt = tilt; + obj.azimuth = azimuth; end \ No newline at end of file diff --git a/geometries/@cone/plot.m b/geometries/@cone/plot.m index 83098db..43b55a6 100644 --- a/geometries/@cone/plot.m +++ b/geometries/@cone/plot.m @@ -20,7 +20,18 @@ function [obj, f] = plot(obj, ind, f, maxAlt) % Scale to match height Z = Z * maxAlt; - + + % Rotate mesh around apex to match boresight tilt and azimuth. + % Apex sits at [0, 0, maxAlt] before center translation. + % Convention: tilt 0=nadir, 90=horizon; azimuth 0=+Y, 90=+X, clockwise. + Ry = [cosd(obj.tilt), 0, -sind(obj.tilt); 0, 1, 0; sind(obj.tilt), 0, cosd(obj.tilt)]; + Rz = [sind(obj.azimuth), -cosd(obj.azimuth), 0; cosd(obj.azimuth), sind(obj.azimuth), 0; 0, 0, 1]; + R = Rz * Ry; + pts = R * [X(:)'; Y(:)'; Z(:)' - maxAlt]; + X = reshape(pts(1, :), size(X)); + Y = reshape(pts(2, :), size(Y)); + Z = reshape(pts(3, :) + maxAlt, size(Z)); + % Move to center location X = X + obj.center(1); Y = Y + obj.center(2); diff --git a/test/test_miSim.m b/test/test_miSim.m index dbef0a4..22bd52b 100644 --- a/test/test_miSim.m +++ b/test/test_miSim.m @@ -456,7 +456,70 @@ classdef test_miSim < matlab.unittest.TestCase tc.testClass = tc.testClass.initialize(tc.domain, tc.agents, tc.barrierGain, tc.barrierExponent, tc.minAlt, tc.timestep, tc.maxIter, tc.obstacles, tc.makePlots, tc.makeVideo, tc.useDoubleIntegrator, tc.dampingCoeff, tc.useFixedTopology); % Run the simulation - tc.testClass = tc.testClass.run();end + tc.testClass = tc.testClass.run(); + end + function test_single_agent_gradient_ascent_tilted(tc) + % make basic domain + tc.minDimension = 10; % domain size + tc.domain = tc.domain.initialize([zeros(1, 3);tc.minDimension* ones(1, 3)], REGION_TYPE.DOMAIN, "Domain"); + + % make basic sensing objective + tc.domain.objective = tc.domain.objective.initialize(objectiveFunctionWrapper([7, 6]), tc.domain, tc.discretizationStep, tc.protectedRange, 1e-6, [7, 6]); + + % Initialize agent collision geometry + tc.agents = {agent}; + geometry1 = spherical; + geometry1 = geometry1.initialize([tc.domain.center(1:2)-tc.domain.dimensions(1)/4, 3], tc.collisionRanges(1), REGION_TYPE.COLLISION); + + % Initialize agent sensor model with fixed parameters + tc.sensor = tc.sensor.initialize(tc.minDimension / 2, 3, 20, 3, 25, 155); + + % Initialize agents + tc.maxIter = 75; + tc.agents{1} = tc.agents{1}.initialize([tc.domain.center(1:2)-tc.domain.dimensions(1)/4, 3], geometry1, tc.sensor, tc.commsRanges(1), tc.maxIter, tc.initialStepSize); + + % Initialize the simulation + tc.obstacles = cell(0, 1); + tc.testClass = tc.testClass.initialize(tc.domain, tc.agents, tc.barrierGain, tc.barrierExponent, tc.minAlt, tc.timestep, tc.maxIter, tc.obstacles, tc.makePlots, tc.makeVideo, tc.useDoubleIntegrator, tc.dampingCoeff, tc.useFixedTopology); + + % Run the simulation + tc.testClass = tc.testClass.run(); + end + function test_single_agent_gradient_ascent_tilted_RF_sensor(tc) + % make basic domain + tc.minDimension = 10; % domain size + tc.domain = tc.domain.initialize([zeros(1, 3);tc.minDimension* ones(1, 3)], REGION_TYPE.DOMAIN, "Domain"); + + % make basic sensing objective + minimumSINR = 50; % (dB) + tc.domain.objective = tc.domain.objective.initialize(objectiveFunctionWrapper([7, 6]), tc.domain, tc.discretizationStep, tc.protectedRange, minimumSINR, [7, 6]); + + % Initialize agent collision geometry + tc.agents = {agent}; + geometry1 = spherical; + geometry1 = geometry1.initialize([tc.domain.center(1:2)-tc.domain.dimensions(1)/4, 3], tc.collisionRanges(1), REGION_TYPE.COLLISION); + + % Initialize agent sensor model with fixed parameters + P_TX = 1e-3; % Transmit power (Watts) + BW = 20e6; % Bandwidth (Hz) + f_c = 2e9; % Center frequency (Hz) + G_RX_dBi = 3; % Receiving Antenna Gain (dBi) + + tc.sensor = rfSensor; + tc.sensor = tc.sensor.initialize(P_TX, BW, f_c, G_RX_dBi, 45, 45); + + % Initialize agents + tc.maxIter = 75; + tc.agents{1} = tc.agents{1}.initialize([tc.domain.center(1:2)-tc.domain.dimensions(1)/4, 3], geometry1, tc.sensor, tc.commsRanges(1), tc.maxIter, tc.initialStepSize); + + % Initialize the simulation + tc.obstacles = cell(0, 1); + tc.minAlt = 0.5; + tc.testClass = tc.testClass.initialize(tc.domain, tc.agents, tc.barrierGain, tc.barrierExponent, tc.minAlt, tc.timestep, tc.maxIter, tc.obstacles, tc.makePlots, tc.makeVideo, tc.useDoubleIntegrator, tc.dampingCoeff, tc.useFixedTopology); + + % Run the simulation + tc.testClass = tc.testClass.run(); + end function test_collision_avoidance(tc) % No obstacles % Fixed agent initial conditions From 8200dab499c68a598b1d1d6f11c89a85b93a0bf2 Mon Sep 17 00:00:00 2001 From: Kevin D Date: Sun, 3 May 2026 14:35:30 -0700 Subject: [PATCH 20/32] fix init signature --- @rfSensor/rfSensor.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/@rfSensor/rfSensor.m b/@rfSensor/rfSensor.m index 07f7841..66ce3b2 100644 --- a/@rfSensor/rfSensor.m +++ b/@rfSensor/rfSensor.m @@ -19,7 +19,7 @@ classdef rfSensor end methods (Access = public) - [obj] = initialize(obj, txPower, bandwidth, centerFreq, rxGain); % initialize sensor, define parameters + [obj] = initialize(obj, txPower, bandwidth, centerFreq, rxGain, 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, t, a] = computePointToPoints(obj, agentPos, targetPos); [value] = halfAngle(obj); % tilt angle (deg) at which sensor performance is halved From ea111e56f86c374595873cdace89eadda02dc0ce Mon Sep 17 00:00:00 2001 From: Kevin D Date: Wed, 6 May 2026 13:01:48 -0700 Subject: [PATCH 21/32] cleanup --- .../7wWFVcqPEpBA2edOtCoXYRUzhTAd.xml | 6 ++++++ .../7wWFVcqPEpBA2edOtCoXYRUzhTAp.xml | 2 ++ .../VtHVtnVxsd3b5Abj2jXLEoe6skMd.xml | 6 ++++++ .../VtHVtnVxsd3b5Abj2jXLEoe6skMp.xml | 2 ++ .../W5VZZvYGRyYmGtZeTv2swuCWOZ8d.xml | 6 ++++++ .../W5VZZvYGRyYmGtZeTv2swuCWOZ8p.xml | 2 ++ .../WZmh1oISZ4y-rj3ybGXq16hboAYd.xml | 6 ++++++ .../WZmh1oISZ4y-rj3ybGXq16hboAYp.xml | 2 ++ .../gP3qjg8gQH7Si3Dbi4abPbO-xfwd.xml | 6 ++++++ .../gP3qjg8gQH7Si3Dbi4abPbO-xfwp.xml | 2 ++ test/test_rfSensor.m | 1 - 11 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/7wWFVcqPEpBA2edOtCoXYRUzhTAd.xml create mode 100644 resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/7wWFVcqPEpBA2edOtCoXYRUzhTAp.xml create mode 100644 resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/VtHVtnVxsd3b5Abj2jXLEoe6skMd.xml create mode 100644 resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/VtHVtnVxsd3b5Abj2jXLEoe6skMp.xml create mode 100644 resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/W5VZZvYGRyYmGtZeTv2swuCWOZ8d.xml create mode 100644 resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/W5VZZvYGRyYmGtZeTv2swuCWOZ8p.xml create mode 100644 resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/WZmh1oISZ4y-rj3ybGXq16hboAYd.xml create mode 100644 resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/WZmh1oISZ4y-rj3ybGXq16hboAYp.xml create mode 100644 resources/project/o6vRLbVRBKxltajotIPvuRfoO58/gP3qjg8gQH7Si3Dbi4abPbO-xfwd.xml create mode 100644 resources/project/o6vRLbVRBKxltajotIPvuRfoO58/gP3qjg8gQH7Si3Dbi4abPbO-xfwp.xml diff --git a/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/7wWFVcqPEpBA2edOtCoXYRUzhTAd.xml b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/7wWFVcqPEpBA2edOtCoXYRUzhTAd.xml new file mode 100644 index 0000000..99772b4 --- /dev/null +++ b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/7wWFVcqPEpBA2edOtCoXYRUzhTAd.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/7wWFVcqPEpBA2edOtCoXYRUzhTAp.xml b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/7wWFVcqPEpBA2edOtCoXYRUzhTAp.xml new file mode 100644 index 0000000..050bb07 --- /dev/null +++ b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/7wWFVcqPEpBA2edOtCoXYRUzhTAp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/VtHVtnVxsd3b5Abj2jXLEoe6skMd.xml b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/VtHVtnVxsd3b5Abj2jXLEoe6skMd.xml new file mode 100644 index 0000000..99772b4 --- /dev/null +++ b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/VtHVtnVxsd3b5Abj2jXLEoe6skMd.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/VtHVtnVxsd3b5Abj2jXLEoe6skMp.xml b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/VtHVtnVxsd3b5Abj2jXLEoe6skMp.xml new file mode 100644 index 0000000..bf019e3 --- /dev/null +++ b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/VtHVtnVxsd3b5Abj2jXLEoe6skMp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/W5VZZvYGRyYmGtZeTv2swuCWOZ8d.xml b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/W5VZZvYGRyYmGtZeTv2swuCWOZ8d.xml new file mode 100644 index 0000000..99772b4 --- /dev/null +++ b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/W5VZZvYGRyYmGtZeTv2swuCWOZ8d.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/W5VZZvYGRyYmGtZeTv2swuCWOZ8p.xml b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/W5VZZvYGRyYmGtZeTv2swuCWOZ8p.xml new file mode 100644 index 0000000..6f7f5b7 --- /dev/null +++ b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/W5VZZvYGRyYmGtZeTv2swuCWOZ8p.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/WZmh1oISZ4y-rj3ybGXq16hboAYd.xml b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/WZmh1oISZ4y-rj3ybGXq16hboAYd.xml new file mode 100644 index 0000000..99772b4 --- /dev/null +++ b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/WZmh1oISZ4y-rj3ybGXq16hboAYd.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/WZmh1oISZ4y-rj3ybGXq16hboAYp.xml b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/WZmh1oISZ4y-rj3ybGXq16hboAYp.xml new file mode 100644 index 0000000..8d0d53a --- /dev/null +++ b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/WZmh1oISZ4y-rj3ybGXq16hboAYp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/o6vRLbVRBKxltajotIPvuRfoO58/gP3qjg8gQH7Si3Dbi4abPbO-xfwd.xml b/resources/project/o6vRLbVRBKxltajotIPvuRfoO58/gP3qjg8gQH7Si3Dbi4abPbO-xfwd.xml new file mode 100644 index 0000000..99772b4 --- /dev/null +++ b/resources/project/o6vRLbVRBKxltajotIPvuRfoO58/gP3qjg8gQH7Si3Dbi4abPbO-xfwd.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/resources/project/o6vRLbVRBKxltajotIPvuRfoO58/gP3qjg8gQH7Si3Dbi4abPbO-xfwp.xml b/resources/project/o6vRLbVRBKxltajotIPvuRfoO58/gP3qjg8gQH7Si3Dbi4abPbO-xfwp.xml new file mode 100644 index 0000000..6f7f5b7 --- /dev/null +++ b/resources/project/o6vRLbVRBKxltajotIPvuRfoO58/gP3qjg8gQH7Si3Dbi4abPbO-xfwp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/test/test_rfSensor.m b/test/test_rfSensor.m index fa7a988..6323aa3 100644 --- a/test/test_rfSensor.m +++ b/test/test_rfSensor.m @@ -82,7 +82,6 @@ classdef test_rfSensor < matlab.unittest.TestCase tc.testClass.plotPerformance(altitude, otherSensorsPos, otherSensors); end - function plot_SINR_heterogenous_interferers_efficiently(tc) P_TX = 1e-3; BW = 20e6; From bc26cbc7061270522cc0d7263d2abc410a35a712 Mon Sep 17 00:00:00 2001 From: Kevin D Date: Wed, 6 May 2026 17:16:57 -0700 Subject: [PATCH 22/32] rfsensor parameterization --- @rfSensor/RSS.m | 2 +- @rfSensor/initialize.m | 12 +++++++++--- @rfSensor/pathLoss.m | 2 +- @rfSensor/plotParameters.m | 2 +- @rfSensor/plotPerformance.m | 2 +- @rfSensor/rfSensor.m | 4 +++- @rfSensor/transmitterGain.m | 4 +--- @sensingObjective/plot.m | 2 +- test/test_rfSensor.m | 28 +++++++++++++++++++--------- 9 files changed, 37 insertions(+), 21 deletions(-) diff --git a/@rfSensor/RSS.m b/@rfSensor/RSS.m index c512a56..ef05692 100644 --- a/@rfSensor/RSS.m +++ b/@rfSensor/RSS.m @@ -10,6 +10,6 @@ function value = RSS(obj, d, t, a) end assert(size(d, 1) == size(t, 1), "Mismatch in number of distances (%d) and tilts (%d) provided", size(d, 1), size(t, 1)); - % RSS (dBm) = TX Power (dBm) + TX Antenna Gain (dB) + RX Antenna Gain (dBi) - Path Loss (dB) + % RSS (dBm) = TX Power (dBm) + TX Antenna Gain (dBi) + RX Antenna Gain (dBi) - Path Loss (dB) value = obj.P_TX_dBm + obj.transmitterGain(t, a) + obj.G_RX_dBi - obj.pathLoss(d); end \ No newline at end of file diff --git a/@rfSensor/initialize.m b/@rfSensor/initialize.m index 14db6f3..156d4f1 100644 --- a/@rfSensor/initialize.m +++ b/@rfSensor/initialize.m @@ -1,26 +1,32 @@ -function obj = initialize(obj, txPower, bandwidth, centerFreq, rxGain_dBi, tilt, azimuth) +function obj = initialize(obj, txPower, bandwidth, centerFreq, rxGain_dBi, beamwidthExponent, tilt, azimuth, lossExponent) arguments (Input) obj (1, 1) {mustBeA(obj, "rfSensor")} txPower (1, 1) double; bandwidth (1, 1) double; centerFreq (1, 1) double; rxGain_dBi (1, 1) double; + beamwidthExponent (1, 1) double; tilt (1, 1) double = 0; azimuth (1, 1) double = 0; + lossExponent (1, 1) double = NaN; end arguments (Output) obj (1, 1) {mustBeA(obj, "rfSensor")} end - % Provided values + %% Provided values obj.P_TX = txPower; % Transmit power (W) obj.BW = bandwidth; % Bandwidth (Hz) obj.f_c = centerFreq; % Center frequency (Hz) obj.G_RX_dBi = rxGain_dBi; % Receiving Antenna Gain (dBi) + obj.beamwidthExponent = beamwidthExponent; % Defines how focused the antenna beam is + obj.lossExponent = lossExponent; + + % Define initial antenna pointing obj.tilt = tilt; obj.azimuth = azimuth; - % Computed values + %% 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 end \ No newline at end of file diff --git a/@rfSensor/pathLoss.m b/@rfSensor/pathLoss.m index c638699..9970f00 100644 --- a/@rfSensor/pathLoss.m +++ b/@rfSensor/pathLoss.m @@ -8,6 +8,6 @@ function L_FSPL_dB = pathLoss(obj, d) end % Free Space Path Loss (dB); d clamped away from zero (log undefined at d=0) - L_FSPL_dB = 20*log10(max(d, eps)) + 20*log10(obj.f_c) + 20*log10((4*pi)/obj.c); + L_FSPL_dB = obj.lossExponent * 10 * log10(max(d, eps)) + 20 * log10(obj.f_c) + 20 * log10((4*pi)/obj.c); end diff --git a/@rfSensor/plotParameters.m b/@rfSensor/plotParameters.m index 002aab9..5f24a6e 100644 --- a/@rfSensor/plotParameters.m +++ b/@rfSensor/plotParameters.m @@ -40,7 +40,7 @@ function f = plotParameters(obj) end colormap(turbo); - colorbar; + c = colorbar; c.Label.String = "Received Signal Strength (dB)"; daspect([1 1 0.2]); xlabel('X (log_{10} units)'); ylabel('Y (log_{10} units)'); zlabel('log_{10} Altitude (m)'); set(gca, 'ZDir', 'reverse'); diff --git a/@rfSensor/plotPerformance.m b/@rfSensor/plotPerformance.m index df9dfe9..f36dcc1 100644 --- a/@rfSensor/plotPerformance.m +++ b/@rfSensor/plotPerformance.m @@ -52,7 +52,7 @@ function f = plotPerformance(obj, altitude, otherSensorsPos, otherSensors) colorbar; xlabel("X (m)"); ylabel("Y (m)"); title("Linearly Normalized SNR (dB)"); subtitle("No interfering sources"); - addSensorOverlay(gca, sensorXY, sensorTilts, sensorAzimuths, tailScale); + addSensorOverlay(gca, sensorXY(1, 1:2), sensorTilts(1, 1), sensorAzimuths(1, 1), tailScale); nexttile; imagesc(distances, distances, SINR); diff --git a/@rfSensor/rfSensor.m b/@rfSensor/rfSensor.m index 66ce3b2..0cc33ef 100644 --- a/@rfSensor/rfSensor.m +++ b/@rfSensor/rfSensor.m @@ -4,6 +4,7 @@ classdef rfSensor c = 3e8; % Speed of light (m/s) k_B = 1.38e-23 % Boltzmann constant (W/Hz/K) for thermal noise model T_0 = 300; % Ambient temperature (Kelvin) for thermal noise model + lossExponent = NaN; % Path loss exponent (2 for free space, up to 6 for the lossiest environments) % Sensor parameters P_TX = NaN; % Transmit power (Watts) BW = NaN; % Bandwidth (Hz) @@ -11,6 +12,7 @@ classdef rfSensor G_RX_dBi = NaN; % Receiver antenna gain tilt = NaN; % Antenna boresight tilt (deg): 0=nadir, 90=horizon azimuth = NaN; % Antenna boresight azimuth (deg): 0=+y, 90=+x, 180=-y, 270=-x + beamwidthExponent = NaN; % Antenna beamwidth exponent for cosine radiation pattern, larger exponent -> narrower beam % Values computed at initialization P_TX_dBm = NaN; % Transmit power (dBm) N = NaN; % Thermal noise @@ -19,7 +21,7 @@ classdef rfSensor end methods (Access = public) - [obj] = initialize(obj, txPower, bandwidth, centerFreq, rxGain, tilt, azimuth); % initialize sensor, define parameters + [obj] = initialize(obj, txPower, 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, t, a] = computePointToPoints(obj, agentPos, targetPos); [value] = halfAngle(obj); % tilt angle (deg) at which sensor performance is halved diff --git a/@rfSensor/transmitterGain.m b/@rfSensor/transmitterGain.m index 06e0d68..6317608 100644 --- a/@rfSensor/transmitterGain.m +++ b/@rfSensor/transmitterGain.m @@ -11,8 +11,6 @@ function value = transmitterGain(obj, t, a) error("t and a must be the same size"); end - n = 6; % beamwidth exponent (higher = narrower beam) - % 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) + ... @@ -21,5 +19,5 @@ function value = transmitterGain(obj, t, a) theta = acosd(cos_theta); % Cardioid family: peak at boresight (theta=0), null opposite (theta=180°) - value = 10 .* n .* log10((1 + cosd(theta)) ./ 2); + value = 10 .* obj.beamwidthExponent .* log10((1 + cosd(theta)) ./ 2); end diff --git a/@sensingObjective/plot.m b/@sensingObjective/plot.m index d7bc074..ee5ed39 100644 --- a/@sensingObjective/plot.m +++ b/@sensingObjective/plot.m @@ -20,7 +20,7 @@ function f = plot(obj, ind, f) hold(f.CurrentAxes, "off"); else hold(f.Children(1).Children(ind(1)), "on"); - o = surf(f.Children(1).Children(ind(1)), obj.X, obj.Y, zeros(size(obj.X)), obj.values ./ max(obj.values, [], "all"), "EdgeColor", "none"); + o = surf(f.Children(1).Children(ind(1)), obj.X, obj.Y, zeros(size(obj.X)), obj.values ./ sum(obj.values, "all"), "EdgeColor", "none"); o.HitTest = "off"; o.PickableParts = "none"; hold(f.Children(1).Children(ind(1)), "off"); diff --git a/test/test_rfSensor.m b/test/test_rfSensor.m index 6323aa3..cad8b2e 100644 --- a/test/test_rfSensor.m +++ b/test/test_rfSensor.m @@ -18,8 +18,10 @@ classdef test_rfSensor < matlab.unittest.TestCase 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, 0, 0); + tc.testClass = tc.testClass.initialize(P_TX, BW, f_c, G_RX_dBi, beamwidthExponent, 0, 0, lossExponent); tc.testClass.plotParameters(); end @@ -29,8 +31,10 @@ classdef test_rfSensor < matlab.unittest.TestCase 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, 30, 135); + tc.testClass = tc.testClass.initialize(P_TX, BW, f_c, G_RX_dBi, beamwidthExponent, 30, 135, lossExponent); altitude = 30; @@ -50,8 +54,10 @@ classdef test_rfSensor < matlab.unittest.TestCase 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, 0, 0); + tc.testClass = tc.testClass.initialize(P_TX, BW, f_c, G_RX_dBi, beamwidthExponent, 0, 0, lossExponent); altitude = 30; otherSensorsPos = [6, -4, -1]; % relative to main sensor @@ -67,8 +73,10 @@ classdef test_rfSensor < matlab.unittest.TestCase 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, 0, 0); + tc.testClass = tc.testClass.initialize(P_TX, BW, f_c, G_RX_dBi, beamwidthExponent, 0, 0, lossExponent); altitude = 30; otherSensorsPos = [6, -4, -1; -2, 6, 0]; % relative to main sensor @@ -77,8 +85,8 @@ 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, 0, 0); - otherSensors{2} = otherSensors{2}.initialize(10 * P_TX, BW, f_c, G_RX_dBi, 0, 0); + 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); tc.testClass.plotPerformance(altitude, otherSensorsPos, otherSensors); end @@ -88,13 +96,15 @@ classdef test_rfSensor < matlab.unittest.TestCase f_c = 2e9; G_RX_dBi = 3; altitude = 30; + beamwidthExponent = [6, 4, 10]; + lossExponent = 2; sensor1 = rfSensor; - sensor1 = sensor1.initialize(P_TX, BW, f_c, G_RX_dBi, 15, 45); + sensor1 = sensor1.initialize(P_TX, BW, f_c, G_RX_dBi, beamwidthExponent(1), 15, 45, lossExponent); sensor2 = rfSensor; - sensor2 = sensor2.initialize(P_TX, BW, f_c, G_RX_dBi, 10, 150); + sensor2 = sensor2.initialize(P_TX, BW, f_c, G_RX_dBi, beamwidthExponent(2), 10, 150, lossExponent); sensor3 = rfSensor; - sensor3 = sensor3.initialize(P_TX, BW, f_c, G_RX_dBi, 20, 200); + sensor3 = sensor3.initialize(P_TX, BW, f_c, G_RX_dBi, beamwidthExponent(3), 20, 200, lossExponent); pos1 = [0, 0, altitude]; pos2 = [6, -4, altitude - 1]; From 743331039028626d23d255126254e0cec8528e2b Mon Sep 17 00:00:00 2001 From: Kevin D Date: Wed, 6 May 2026 17:42:11 -0700 Subject: [PATCH 23/32] trimmed radio plot to exclude setup/teardown times --- @rfSensor/rfSensor.m | 4 ++-- aerpaw/results/resultsAnalysis.m | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/@rfSensor/rfSensor.m b/@rfSensor/rfSensor.m index 0cc33ef..8cd2131 100644 --- a/@rfSensor/rfSensor.m +++ b/@rfSensor/rfSensor.m @@ -17,7 +17,7 @@ classdef rfSensor P_TX_dBm = NaN; % Transmit power (dBm) N = NaN; % Thermal noise % Cached state (per timestep) - rssCache (:,1) double = double.empty(0,1); % linear-scale RSS to last target grid + rssCache (:,1) double = double.empty(0,1); % linear-scale RSS to last ground targets grid end methods (Access = public) @@ -31,7 +31,7 @@ classdef rfSensor end methods (Access = private) x = RSS(obj, d, t, a); % Received signal strength (function of distance and tilt angle) - G_TX_dB = transmitterGain(obj, t, a); % TODO Antenna gain for a given TX/RX pair + G_TX_dB = transmitterGain(obj, t, a); % Antenna gain for a given TX/RX pair 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/aerpaw/results/resultsAnalysis.m b/aerpaw/results/resultsAnalysis.m index 2c3617d..96e5f45 100644 --- a/aerpaw/results/resultsAnalysis.m +++ b/aerpaw/results/resultsAnalysis.m @@ -28,6 +28,7 @@ plotWholeFlight = true; % do not attempt to automatically trim initial and final % Plot radio statistics [fRadio, R] = plotRadioLogs(resultsPath); +set(findobj(fRadio, 'Type', 'axes'), 'XLim', controller.timestamp([1, end])); %% Run simulation % Run miSim using same AERPAW scenario definition CSV From 740b42eba443b4acee41e5170eb03b6319dae771 Mon Sep 17 00:00:00 2001 From: Kevin D Date: Wed, 6 May 2026 18:12:40 -0700 Subject: [PATCH 24/32] plot radio metrics as a function of distance --- aerpaw/results/plotRadioLogs.m | 108 +++++++++++++++++++++++++++++-- aerpaw/results/resultsAnalysis.m | 5 +- 2 files changed, 106 insertions(+), 7 deletions(-) diff --git a/aerpaw/results/plotRadioLogs.m b/aerpaw/results/plotRadioLogs.m index 5a59d94..7371d73 100644 --- a/aerpaw/results/plotRadioLogs.m +++ b/aerpaw/results/plotRadioLogs.m @@ -1,9 +1,12 @@ -function [f, R] = plotRadioLogs(resultsPath) +function [f, fDist, R] = plotRadioLogs(resultsPath, G, tLim) arguments (Input) resultsPath (1, 1) string; + G cell = {}; + tLim (1, 2) datetime = [datetime(-Inf, 'ConvertFrom', 'datenum'), datetime(Inf, 'ConvertFrom', 'datenum')]; end arguments (Output) f (1, 1) matlab.ui.Figure; + fDist (1, 1) matlab.ui.Figure; R cell; end @@ -41,6 +44,7 @@ function [f, R] = plotRadioLogs(resultsPath) metricNames = ["SNR", "Power", "Quality", "PathLoss", "NoiseFloor", "FreqOffset"]; yLabels = ["SNR (dB)", "Power (dB)", "Quality", "Path Loss (dB)", "Noise Floor (dB)", "Freq Offset (MHz)"]; + % --- Time-based figure --- f = figure; tl = tiledlayout(numel(metricNames), 1, 'TileSpacing', 'compact', 'Padding', 'compact'); @@ -57,10 +61,10 @@ function [f, R] = plotRadioLogs(resultsPath) for ti = 1:numel(txIDs) txID = txIDs(ti); rows = tbl(tbl.TxUAVID == txID, :); + rows = rows(rows.Timestamp >= tLim(1) & rows.Timestamp <= tLim(2), :); vals = rows.(metricNames(mi)); - % Skip if all NaN for this metric - if all(isnan(vals)) + if isempty(rows) || all(isnan(vals)) continue; end @@ -81,4 +85,100 @@ function [f, R] = plotRadioLogs(resultsPath) end title(tl, 'Radio Channel Metrics'); -end \ No newline at end of file + + % --- Distance-based figure --- + fDist = figure; + + if isempty(G) + return; + end + + tl2 = tiledlayout(numel(metricNames), 1, 'TileSpacing', 'compact', 'Padding', 'compact'); + + for mi = 1:numel(metricNames) + ax = nexttile(tl2); + hold(ax, 'on'); + grid(ax, 'on'); + + legendEntries = string.empty; + ci = 1; + for rxIdx = 1:nUAV + tbl = R{rxIdx}; + txIDs = unique(tbl.TxUAVID); + for ti = 1:numel(txIDs) + txID = txIDs(ti); + rows = tbl(tbl.TxUAVID == txID, :); + + if isempty(rows) + continue; + end + + rows = rows(rows.Timestamp >= tLim(1) & rows.Timestamp <= tLim(2), :); + if isempty(rows) + continue; + end + + vals = rows.(metricNames(mi)); + if all(isnan(vals)) + continue; + end + + % Map 0-based UAV IDs to 1-based GPS cell indices + txGpsIdx = double(txID) + 1; + rxGpsIdx = double(rows.RxUAVID(1)) + 1; + + if txGpsIdx > numel(G) || rxGpsIdx > numel(G) + continue; + end + + Gtx = G{txGpsIdx}; + Grx = G{rxGpsIdx}; + + if ~ismember('East', Gtx.Properties.VariableNames) || ... + ~ismember('East', Grx.Properties.VariableNames) + continue; + end + + % Strip timezone before posixtime so radio and GPS timestamps + % are treated on the same scale (both are AERPAW wall-clock time) + txTs = Gtx.Timestamp; txTs.TimeZone = ''; + rxTs = Grx.Timestamp; rxTs.TimeZone = ''; + txPt = posixtime(txTs); + rxPt = posixtime(rxTs); + radioPt = posixtime(rows.Timestamp); + + % Interpolate GPS positions at radio measurement times. + % Exclude NaN ENU entries (outside algorithm flight range). + validTx = ~isnan(Gtx.East); + validRx = ~isnan(Grx.East); + + txE = interp1(txPt(validTx), Gtx.East(validTx), radioPt, 'linear', NaN); + txN = interp1(txPt(validTx), Gtx.North(validTx), radioPt, 'linear', NaN); + txU = interp1(txPt(validTx), Gtx.Up(validTx), radioPt, 'linear', NaN); + rxE = interp1(rxPt(validRx), Grx.East(validRx), radioPt, 'linear', NaN); + rxN = interp1(rxPt(validRx), Grx.North(validRx), radioPt, 'linear', NaN); + rxU = interp1(rxPt(validRx), Grx.Up(validRx), radioPt, 'linear', NaN); + + dist = vecnorm([txE - rxE, txN - rxN, txU - rxU], 2, 2); + + if all(isnan(dist)) + continue; + end + + si = mod(ci - 1, numel(styles)) + 1; + scatter(ax, dist, vals, 9, colors(ci, :), strrep(styles(si), "-", ""), 'filled'); + legendEntries(end+1) = sprintf("TX %d → RX %d", txID, rows.RxUAVID(1)); %#ok + ci = ci + 1; + end + end + + ylabel(ax, yLabels(mi)); + if mi == numel(metricNames) + xlabel(ax, 'Distance (m)'); + end + legend(ax, legendEntries, 'Location', 'best'); + hold(ax, 'off'); + end + + title(tl2, 'Radio Channel Metrics vs Distance'); +end diff --git a/aerpaw/results/resultsAnalysis.m b/aerpaw/results/resultsAnalysis.m index 96e5f45..2960bd1 100644 --- a/aerpaw/results/resultsAnalysis.m +++ b/aerpaw/results/resultsAnalysis.m @@ -26,9 +26,8 @@ seaToGroundLevel = 110; % measured approximately from USGS national map viewer plotWholeFlight = true; % do not attempt to automatically trim initial and final positioning and landing from flight plot (buggy) [fGlobe, G] = plotGpsLogs(resultsPath, seaToGroundLevel, true); -% Plot radio statistics -[fRadio, R] = plotRadioLogs(resultsPath); -set(findobj(fRadio, 'Type', 'axes'), 'XLim', controller.timestamp([1, end])); +% Plot radio statistics (time-based and distance-based) +[fRadio, fRadioDist, R] = plotRadioLogs(resultsPath, G, controller.timestamp([1, end])); %% Run simulation % Run miSim using same AERPAW scenario definition CSV From b44df40c7e9a6505287ea4303394b36d20557b05 Mon Sep 17 00:00:00 2001 From: Kevin D Date: Thu, 7 May 2026 09:04:52 -0700 Subject: [PATCH 25/32] added sensor pointing by gradient ascent --- @agent/agent.m | 2 + @agent/initialize.m | 5 +- @agent/run.m | 87 ++++++---- @agent/updatePlots.m | 79 ++++++--- @miSim/initialize.m | 4 +- @miSim/miSim.m | 1 + @miSim/run.m | 2 +- @rfSensor/rfSensor.m | 6 +- @sensingObjective/plot.m | 19 ++- @sigmoidSensor/sigmoidSensor.m | 5 +- aerpaw/guidance_step.m | 4 +- aerpaw/results/controllerAnalysis.m | 28 +++ aerpaw/results/resultsAnalysis.m | 28 +-- .../tmG0S9kw2cno3kVq1hMKT8-rXXkd.xml | 6 + .../tmG0S9kw2cno3kVq1hMKT8-rXXkp.xml | 2 + test/parametricTestSuite.m | 6 +- test/test_miSim.m | 160 +++++++++++++----- 17 files changed, 296 insertions(+), 148 deletions(-) create mode 100644 aerpaw/results/controllerAnalysis.m create mode 100644 resources/project/cCclYJTOop6jkdZsItlf7iNuov4/tmG0S9kw2cno3kVq1hMKT8-rXXkd.xml create mode 100644 resources/project/cCclYJTOop6jkdZsItlf7iNuov4/tmG0S9kw2cno3kVq1hMKT8-rXXkp.xml diff --git a/@agent/agent.m b/@agent/agent.m index 1fc1ecd..e8db7ed 100644 --- a/@agent/agent.m +++ b/@agent/agent.m @@ -32,7 +32,9 @@ classdef agent properties (SetAccess = private, GetAccess = public) initialStepSize = NaN; + initialMaxAngleStepSize = NaN; stepDecayRate = NaN; + angleStepDecayRate = NaN; end methods (Access = public) diff --git a/@agent/initialize.m b/@agent/initialize.m index 47c3e98..a5da81d 100644 --- a/@agent/initialize.m +++ b/@agent/initialize.m @@ -1,4 +1,4 @@ -function obj = initialize(obj, pos, collisionGeometry, sensorModel, comRange, maxIter, initialStepSize, label, plotCommsGeometry) +function obj = initialize(obj, pos, collisionGeometry, sensorModel, comRange, maxIter, initialStepSize, initialMaxAngleStepSize, label, plotCommsGeometry) arguments (Input) obj (1, 1) {mustBeA(obj, "agent")}; pos (1, 3) double; @@ -7,6 +7,7 @@ function obj = initialize(obj, pos, collisionGeometry, sensorModel, comRange, ma comRange (1, 1) double; maxIter (1, 1) double; initialStepSize (1, 1) double = 0.2; + initialMaxAngleStepSize (1, 1) double = 5.0; label (1, 1) string = ""; plotCommsGeometry (1, 1) logical = false; end @@ -23,7 +24,9 @@ function obj = initialize(obj, pos, collisionGeometry, sensorModel, comRange, ma obj.label = label; obj.plotCommsGeometry = plotCommsGeometry; obj.initialStepSize = initialStepSize; + obj.initialMaxAngleStepSize = initialMaxAngleStepSize; obj.stepDecayRate = obj.initialStepSize / maxIter; + obj.angleStepDecayRate = obj.initialMaxAngleStepSize / maxIter; % Initialize performance vector if coder.target('MATLAB') diff --git a/@agent/run.m b/@agent/run.m index 340b4cf..5852520 100644 --- a/@agent/run.m +++ b/@agent/run.m @@ -1,14 +1,14 @@ -function obj = run(obj, domain, partitioning, timestepIndex, index, agents, useDoubleIntegrator, dampingCoeff, dt) +function obj = run(obj, domain, partitioning, timestepIndex, index, useDoubleIntegrator, dampingCoeff, dt, optimizeSensorPointing) arguments (Input) obj (1, 1) {mustBeA(obj, "agent")}; domain (1, 1) {mustBeGeometry}; partitioning (:, :) double; timestepIndex (1, 1) double; index (1, 1) double; - agents (:, 1) {mustBeA(agents, "cell")}; useDoubleIntegrator (1, 1) logical = false; dampingCoeff (1, 1) double = 2.0; dt (1, 1) double = 1.0; + optimizeSensorPointing (1, 1) logical = false; end arguments (Output) obj (1, 1) {mustBeA(obj, "agent")}; @@ -33,34 +33,32 @@ function obj = run(obj, domain, partitioning, timestepIndex, index, agents, useD maskedX = domain.objective.X(partitionMask); maskedY = domain.objective.Y(partitionMask); - % Compute agent performance at the current position and each delta position +/- X, Y, Z - delta = domain.objective.discretizationStep; % smallest possible step size that gets different results - deltaApplicator = [0, 0, 0; 1, 0, 0; -1, 0, 0; 0, 1, 0; 0, -1, 0; 0, 0, 1; 0, 0, -1]; % none, +X, -X, +Y, -Y, +Z, -Z - C_delta = NaN(7, 1); % agent performance at delta steps in each direction - for ii = 1:7 + if optimizeSensorPointing + % Stash actual current sensor model tilt/azimuth before messing with it + % in these following hypotheticals + tilt = obj.sensorModel.tilt; + azimuth = obj.sensorModel.azimuth; + end + + % Compute agent performance at the current position and each delta position +/- X, Y, Z, tilt, azimuth + deltaPos = domain.objective.discretizationStep; % smallest possible step size that gets different results + if optimizeSensorPointing + deltaAngle = atan2d(domain.objective.discretizationStep, obj.pos(3)); % smallest possible angle derived from smallest possible step size and current height + end + deltaApplicator = [0, 0, 0, 0, 0; 1, 0, 0, 0, 0; -1, 0, 0, 0, 0; 0, 1, 0, 0, 0; 0, -1, 0, 0, 0; 0, 0, 1, 0, 0; 0, 0, -1, 0, 0; 0, 0, 0, 1, 0; 0, 0, 0, -1, 0; 0, 0, 0, 0, 1; 0, 0, 0, 0, -1;]; % none, +X, -X, +Y, -Y, +Z, -Z, +tilt, -tilt, +azimuth, -azimuth + C_delta = NaN(size(deltaApplicator, 1), 1); % agent performance at delta steps in each direction + for ii = 1:size(deltaApplicator, 1) + if ~optimizeSensorPointing && ii > 7; break; end % Apply delta to position - pos = obj.pos + delta * deltaApplicator(ii, 1:3); + pos = obj.pos + deltaPos * deltaApplicator(ii, 1:3); + if optimizeSensorPointing + % Apply delta to tilt and azimuth + obj.sensorModel.tilt = tilt + deltaAngle * deltaApplicator(ii, 4); + obj.sensorModel.azimuth = azimuth + deltaAngle * deltaApplicator(ii, 5); + end % Compute performance values on partition - if ii < 6 - % Compute sensing performance - sensorValues = obj.sensorModel.sensorPerformance(pos, [maskedX, maskedY, zeros(size(maskedX))]); % S_n(omega, P_n) on W_n - % Objective performance does not change for 0, +/- X, +/- Y steps. - % Those values are computed once before the loop and are only - % recomputed when +/- Z steps are applied - else - % Redo partitioning for Z stepping only - partitioning = obj.partition(agents, domain.objective); - - % Recompute partiton-derived performance values for objective - partitionMask = partitioning == index; - objectiveValues = domain.objective.values(partitionMask); % f(omega) on W_n - - % Recompute partiton-derived performance values for sensing - maskedX = domain.objective.X(partitionMask); - maskedY = domain.objective.Y(partitionMask); - sensorValues = obj.sensorModel.sensorPerformance(pos, [maskedX, maskedY, zeros(size(maskedX))]); % S_n(omega, P_n) on W_n - end + sensorValues = obj.sensorModel.sensorPerformance(pos, [maskedX, maskedY, zeros(size(maskedX))]); % S_n(omega, P_n) on W_n % Rearrange data into image arrays F = NaN(size(partitionMask)); @@ -73,37 +71,54 @@ function obj = run(obj, domain, partitioning, timestepIndex, index, agents, useD C_delta(ii) = sum(C(~isnan(C))); end + if optimizeSensorPointing + % Reset sensor model to actual tilt and azimuth angles + obj.sensorModel.tilt = tilt; + obj.sensorModel.azimuth = azimuth; + end + % Store agent performance at current time and place if coder.target('MATLAB') obj.performance(timestepIndex + 1) = C_delta(1); end % Compute gradient by finite central differences - gradC = [(C_delta(2)-C_delta(3))/(2*delta), (C_delta(4)-C_delta(5))/(2*delta), (C_delta(6)-C_delta(7))/(2*delta)]; + gradC = [(C_delta(2)-C_delta(3))/(2*deltaPos), (C_delta(4)-C_delta(5))/(2*deltaPos), (C_delta(6)-C_delta(7))/(2*deltaPos)]; + if optimizeSensorPointing + gradC(4) = (C_delta(8) -C_delta(9)) /(2*deltaAngle); + gradC(5) = (C_delta(10)-C_delta(11))/(2*deltaAngle); + end % Compute scaling factor - targetRate = obj.initialStepSize - obj.stepDecayRate * timestepIndex; % slow down as you get closer - gradNorm = norm(gradC); + targetPosRate = obj.initialStepSize - obj.stepDecayRate * timestepIndex; % slow down as you get closer + gradPosNorm = norm(gradC(1:3)); % Compute unconstrained next state if useDoubleIntegrator % Double-integrator: gradient produces desired acceleration with damping - if gradNorm < 1e-100 - a_gradient = zeros(1, 3); + if gradPosNorm < 1e-100 + a_gradient = zeros(1, 5); else % Scale so steady-state step ≈ targetRate (matching SI behavior) - a_gradient = (targetRate * dampingCoeff / (gradNorm * dt)) * gradC; + a_gradient = (targetPosRate * dampingCoeff / (gradPosNorm * dt)) * gradC; end % Semi-implicit Euler: unconditionally stable for any dampingCoeff and dt - obj.vel = (obj.vel + a_gradient * dt) / (1 + dampingCoeff * dt); + obj.vel = (obj.vel + a_gradient(1:3) * dt) / (1 + dampingCoeff * dt); obj.pos = obj.lastPos + obj.vel * dt; else % Single-integrator: gradient directly sets position step - if gradNorm >= 1e-100 - obj.pos = obj.pos + (targetRate / gradNorm) * gradC; + if gradPosNorm >= 1e-100 + obj.pos = obj.pos + (targetPosRate / gradPosNorm) * gradC(1:3); end end + % Update tilt and azimuth, saturating at the decaying maximum allowed step size + if optimizeSensorPointing + maxAngleStep = obj.initialMaxAngleStepSize - obj.angleStepDecayRate * timestepIndex; + obj.sensorModel.tilt = obj.sensorModel.tilt + sign(gradC(4)) * min(abs(gradC(4)), maxAngleStep); + obj.sensorModel.azimuth = obj.sensorModel.azimuth + sign(gradC(5)) * min(abs(gradC(5)), maxAngleStep); + end + % Reinitialize collision geometry in the new position d = obj.pos - obj.collisionGeometry.center; if isa(obj.collisionGeometry, "rectangularPrism") diff --git a/@agent/updatePlots.m b/@agent/updatePlots.m index 6548a43..b0b8b7f 100644 --- a/@agent/updatePlots.m +++ b/@agent/updatePlots.m @@ -7,45 +7,68 @@ function updatePlots(obj) % Find change in agent position since last timestep deltaPos = obj.pos - obj.lastPos; - if all(isnan(deltaPos)) || all(deltaPos == zeros(1, 3)) - % Agent did not move, so nothing has to move on the plots + posChanged = ~(all(isnan(deltaPos)) || all(deltaPos == zeros(1, 3))); + orientChanged = obj.sensorModel.tilt ~= obj.fovGeometry.tilt || ... + obj.sensorModel.azimuth ~= obj.fovGeometry.azimuth; + + if ~posChanged && ~orientChanged return; end - % Scatterplot point positions - for ii = 1:size(obj.scatterPoints, 1) - obj.scatterPoints(ii).XData = obj.pos(1); - obj.scatterPoints(ii).YData = obj.pos(2); - obj.scatterPoints(ii).ZData = obj.pos(3); - end - - % Collision geometry edges - for jj = 1:size(obj.collisionGeometry.lines, 2) - % Update plotting - for ii = 1:size(obj.collisionGeometry.lines(:, jj), 1) - obj.collisionGeometry.lines(ii, jj).XData = obj.collisionGeometry.lines(ii, jj).XData + deltaPos(1); - obj.collisionGeometry.lines(ii, jj).YData = obj.collisionGeometry.lines(ii, jj).YData + deltaPos(2); - obj.collisionGeometry.lines(ii, jj).ZData = obj.collisionGeometry.lines(ii, jj).ZData + deltaPos(3); + if posChanged + % Scatterplot point positions + for ii = 1:size(obj.scatterPoints, 1) + obj.scatterPoints(ii).XData = obj.pos(1); + obj.scatterPoints(ii).YData = obj.pos(2); + obj.scatterPoints(ii).ZData = obj.pos(3); end - end - % Communications geometry edges - if obj.plotCommsGeometry - for jj = 1:size(obj.commsGeometry.lines, 2) + % Collision geometry edges + for jj = 1:size(obj.collisionGeometry.lines, 2) for ii = 1:size(obj.collisionGeometry.lines(:, jj), 1) obj.collisionGeometry.lines(ii, jj).XData = obj.collisionGeometry.lines(ii, jj).XData + deltaPos(1); obj.collisionGeometry.lines(ii, jj).YData = obj.collisionGeometry.lines(ii, jj).YData + deltaPos(2); obj.collisionGeometry.lines(ii, jj).ZData = obj.collisionGeometry.lines(ii, jj).ZData + deltaPos(3); end end + + % Communications geometry edges + if obj.plotCommsGeometry + for jj = 1:size(obj.commsGeometry.lines, 2) + for ii = 1:size(obj.collisionGeometry.lines(:, jj), 1) + obj.collisionGeometry.lines(ii, jj).XData = obj.collisionGeometry.lines(ii, jj).XData + deltaPos(1); + obj.collisionGeometry.lines(ii, jj).YData = obj.collisionGeometry.lines(ii, jj).YData + deltaPos(2); + obj.collisionGeometry.lines(ii, jj).ZData = obj.collisionGeometry.lines(ii, jj).ZData + deltaPos(3); + end + end + end end - % Update FOV geometry surfaces - for jj = 1:size(obj.fovGeometry.surface, 2) - % Update each plot - % obj.fovGeometry = obj.fovGeometry.plot(obj.spatialPlotIndices) - obj.fovGeometry.surface(jj).XData = obj.fovGeometry.surface(jj).XData + deltaPos(1); - obj.fovGeometry.surface(jj).YData = obj.fovGeometry.surface(jj).YData + deltaPos(2); - obj.fovGeometry.surface(jj).ZData = obj.fovGeometry.surface(jj).ZData + deltaPos(3); + % FOV cone: recompute full mesh whenever position or orientation changes + if ~isempty(obj.fovGeometry.surface) + % Sync fovGeometry state to current agent position and sensor orientation + obj.fovGeometry = obj.fovGeometry.initialize( ... + obj.pos, obj.fovGeometry.radius, obj.fovGeometry.height, ... + obj.fovGeometry.tag, obj.fovGeometry.label, ... + obj.sensorModel.tilt, obj.sensorModel.azimuth); + + % Recompute cone mesh (mirrors cone.plot logic) + maxAlt = obj.fovGeometry.surface(1).Parent.ZLim(2); + scalingFactor = maxAlt / obj.fovGeometry.height; + [X, Y, Z] = cylinder([scalingFactor * obj.fovGeometry.radius, 0], obj.fovGeometry.n); + Z = Z * maxAlt; + Ry = [cosd(obj.fovGeometry.tilt), 0, -sind(obj.fovGeometry.tilt); 0, 1, 0; sind(obj.fovGeometry.tilt), 0, cosd(obj.fovGeometry.tilt)]; + Rz = [sind(obj.fovGeometry.azimuth), -cosd(obj.fovGeometry.azimuth), 0; cosd(obj.fovGeometry.azimuth), sind(obj.fovGeometry.azimuth), 0; 0, 0, 1]; + R = Rz * Ry; + pts = R * [X(:)'; Y(:)'; Z(:)' - maxAlt]; + X = reshape(pts(1, :), size(X)) + obj.pos(1); + Y = reshape(pts(2, :), size(Y)) + obj.pos(2); + Z = reshape(pts(3, :) + maxAlt, size(Z)) + obj.pos(3) - maxAlt; + + for jj = 1:size(obj.fovGeometry.surface, 2) + obj.fovGeometry.surface(jj).XData = X; + obj.fovGeometry.surface(jj).YData = Y; + obj.fovGeometry.surface(jj).ZData = Z; + end end -end \ No newline at end of file +end diff --git a/@miSim/initialize.m b/@miSim/initialize.m index 08f045f..060bd5f 100644 --- a/@miSim/initialize.m +++ b/@miSim/initialize.m @@ -1,4 +1,4 @@ -function [obj] = initialize(obj, domain, agents, barrierGain, barrierExponent, minAlt, timestep, maxIter, obstacles, makePlots, makeVideo, useDoubleIntegrator, dampingCoeff, useFixedTopology) +function [obj] = initialize(obj, domain, agents, barrierGain, barrierExponent, minAlt, timestep, maxIter, obstacles, makePlots, makeVideo, useDoubleIntegrator, dampingCoeff, useFixedTopology, optimizeSensorPointing) arguments (Input) obj (1, 1) {mustBeA(obj, "miSim")}; domain (1, 1) {mustBeGeometry}; @@ -14,6 +14,7 @@ function [obj] = initialize(obj, domain, agents, barrierGain, barrierExponent, m useDoubleIntegrator (1, 1) logical = false; dampingCoeff (1, 1) double = 2.0; useFixedTopology (1, 1) logical = false; + optimizeSensorPointing (1, 1) logical = false; end arguments (Output) obj (1, 1) {mustBeA(obj, "miSim")}; @@ -93,6 +94,7 @@ function [obj] = initialize(obj, domain, agents, barrierGain, barrierExponent, m obj.useDoubleIntegrator = useDoubleIntegrator; obj.dampingCoeff = dampingCoeff; obj.useFixedTopology = useFixedTopology; + obj.optimizeSensorPointing = optimizeSensorPointing; % Compute adjacency matrix and network topology obj = obj.updateAdjacency(); diff --git a/@miSim/miSim.m b/@miSim/miSim.m index 627ae4e..e9b8f6b 100644 --- a/@miSim/miSim.m +++ b/@miSim/miSim.m @@ -20,6 +20,7 @@ classdef miSim useDoubleIntegrator = false; % false = single-integrator, true = double-integrator dynamics dampingCoeff = 2.0; % velocity-proportional damping for double-integrator mode useFixedTopology = false; % false = lesser neighbor (dynamic), true = fixed initial topology + optimizeSensorPointing = false; % false = fixed sensor tilt/azimuth, true = optimize tilt/azimuth via gradient ascent artifactName = ""; f; % main plotting tiled layout figure fPerf; % performance plot figure diff --git a/@miSim/run.m b/@miSim/run.m index 3427ae9..40ab031 100644 --- a/@miSim/run.m +++ b/@miSim/run.m @@ -42,7 +42,7 @@ function [obj] = run(obj) % Moving % Iterate over agents to simulate their unconstrained motion for jj = 1:size(obj.agents, 1) - obj.agents{jj} = obj.agents{jj}.run(obj.domain, obj.partitioning, obj.timestepIndex, jj, obj.agents, obj.useDoubleIntegrator, obj.dampingCoeff, obj.timestep); + obj.agents{jj} = obj.agents{jj}.run(obj.domain, obj.partitioning, obj.timestepIndex, jj, obj.useDoubleIntegrator, obj.dampingCoeff, obj.timestep, obj.optimizeSensorPointing); end % Adjust motion determined by unconstrained gradient ascent using diff --git a/@rfSensor/rfSensor.m b/@rfSensor/rfSensor.m index 8cd2131..8417303 100644 --- a/@rfSensor/rfSensor.m +++ b/@rfSensor/rfSensor.m @@ -10,8 +10,6 @@ classdef rfSensor BW = NaN; % Bandwidth (Hz) f_c = NaN; % Center frequency (Hz) G_RX_dBi = NaN; % Receiver antenna gain - tilt = NaN; % Antenna boresight tilt (deg): 0=nadir, 90=horizon - azimuth = NaN; % Antenna boresight azimuth (deg): 0=+y, 90=+x, 180=-y, 270=-x beamwidthExponent = NaN; % Antenna beamwidth exponent for cosine radiation pattern, larger exponent -> narrower beam % Values computed at initialization P_TX_dBm = NaN; % Transmit power (dBm) @@ -19,6 +17,10 @@ classdef rfSensor % Cached state (per timestep) rssCache (:,1) double = double.empty(0,1); % linear-scale RSS to last ground targets grid end + properties (Access = public) + tilt = NaN; % Antenna boresight tilt (deg): 0=nadir, 90=horizon + azimuth = NaN; % Antenna boresight azimuth (deg): 0=+y, 90=+x, 180=-y, 270=-x + end methods (Access = public) [obj] = initialize(obj, txPower, bandwidth, centerFreq, rxGain, beamwidthExponent, tilt, azimuth); % initialize sensor, define parameters diff --git a/@sensingObjective/plot.m b/@sensingObjective/plot.m index ee5ed39..baf31b3 100644 --- a/@sensingObjective/plot.m +++ b/@sensingObjective/plot.m @@ -11,19 +11,26 @@ function f = plot(obj, ind, f) % Create axes if they don't already exist f = firstPlotSetup(f); + normalized = obj.values ./ sum(obj.values, "all"); + cRange = [min(normalized, [], "all"), max(normalized, [], "all")]; + % Plot gradient on the "floor" of the domain if isnan(ind) - hold(f.CurrentAxes, "on"); - o = surf(f.CurrentAxes, obj.X, obj.Y, zeros(size(obj.X)), obj.values ./ max(obj.values, [], "all"), "EdgeColor", "none"); + ax = f.CurrentAxes; + hold(ax, "on"); + o = surf(ax, obj.X, obj.Y, zeros(size(obj.X)), normalized, "EdgeColor", "none"); o.HitTest = "off"; o.PickableParts = "none"; - hold(f.CurrentAxes, "off"); + clim(ax, cRange); + hold(ax, "off"); else - hold(f.Children(1).Children(ind(1)), "on"); - o = surf(f.Children(1).Children(ind(1)), obj.X, obj.Y, zeros(size(obj.X)), obj.values ./ sum(obj.values, "all"), "EdgeColor", "none"); + ax = f.Children(1).Children(ind(1)); + hold(ax, "on"); + o = surf(ax, obj.X, obj.Y, zeros(size(obj.X)), normalized, "EdgeColor", "none"); o.HitTest = "off"; o.PickableParts = "none"; - hold(f.Children(1).Children(ind(1)), "off"); + clim(ax, cRange); + hold(ax, "off"); end % Add to other perspectives diff --git a/@sigmoidSensor/sigmoidSensor.m b/@sigmoidSensor/sigmoidSensor.m index 8c7879b..a6fac33 100644 --- a/@sigmoidSensor/sigmoidSensor.m +++ b/@sigmoidSensor/sigmoidSensor.m @@ -5,8 +5,9 @@ classdef sigmoidSensor betaDist = NaN; alphaTilt = NaN; % degrees betaTilt = NaN; - - % pointing parameters + end + properties (Access = public) + % pointing states tilt = 0; azimuth = 0; end diff --git a/aerpaw/guidance_step.m b/aerpaw/guidance_step.m index 25f34df..757d0d5 100644 --- a/aerpaw/guidance_step.m +++ b/aerpaw/guidance_step.m @@ -193,8 +193,8 @@ else % 5. Unconstrained gradient-ascent step for each agent for ii = 1:size(sim.agents, 1) sim.agents{ii} = sim.agents{ii}.run(sim.domain, sim.partitioning, ... - sim.timestepIndex, ii, sim.agents, ... - sim.useDoubleIntegrator, sim.dampingCoeff, sim.timestep); + sim.timestepIndex, ii, ... + sim.useDoubleIntegrator, sim.dampingCoeff, sim.timestep, sim.optimizeSensorPointing); end % 6. Apply CBF safety filter (collision / comms / domain constraints via QP) diff --git a/aerpaw/results/controllerAnalysis.m b/aerpaw/results/controllerAnalysis.m new file mode 100644 index 0000000..c4fd98c --- /dev/null +++ b/aerpaw/results/controllerAnalysis.m @@ -0,0 +1,28 @@ +function controller = controllerAnalysis(resultsPath) + arguments (Input) + resultsPath (1, 1) string; + end + arguments (Output) + controller table; + end + + % Measure intervals between issuing commands from the controller + % (make sure this is ~4-5 seconds at minimum to avoid overwhelming the UAV autopilot) + r = dir(resultsPath); + controllerPath = fullfile(r(startsWith({r.name}, 'controller_')).folder, r(startsWith({r.name}, 'controller_')).name); + controllerPath = dir(controllerPath); + controllerPath = fullfile(controllerPath(endsWith({controllerPath.name}, '_controller_log.txt')).folder, controllerPath(endsWith({controllerPath.name}, '_controller_log.txt')).name); + controller = readControllerLogs(controllerPath); + rpIdx = startsWith(controller.message, "Iteration duration: "); + s = split(controller.message(rpIdx), "Iteration duration: "); + s = split(s(:, 2), ' s'); + s = duration(strcat("00:", s(:, 1)), "InputFormat", "mm:ss.SSS"); + s.Format = "mm:ss.SSS"; + fprintf("Minimum command spacing: %2.3f seconds\n", seconds(min(s))); + fprintf("Maximum command spacing: %2.3f seconds\n", seconds(max(s))); + fprintf("Mean command spacing: %2.3f seconds\n", seconds(mean(s))); + fprintf("Median command spacing: %2.3f seconds\n", seconds(median(s))); + if seconds(min(s)) < 4 + warning("Minimum command spacing %2.3f questionably short for AERPAW", seconds(min(s))); + end +end \ No newline at end of file diff --git a/aerpaw/results/resultsAnalysis.m b/aerpaw/results/resultsAnalysis.m index 2960bd1..a76820c 100644 --- a/aerpaw/results/resultsAnalysis.m +++ b/aerpaw/results/resultsAnalysis.m @@ -1,25 +1,8 @@ %% Plot AERPAW logs (trajectory, radio) resultsPath = fullfile(matlab.project.rootProject().RootFolder, "sandbox", "two_around_wall"); % Define path to results copied from AERPAW platform -% Measure intervals between issuing commands from the controller -% (make sure this is ~4-5 seconds at minimum to avoid overwhelming the UAV autopilot) -r = dir(resultsPath); -controllerPath = fullfile(r(startsWith({r.name}, 'controller_')).folder, r(startsWith({r.name}, 'controller_')).name); -controllerPath = dir(controllerPath); -controllerPath = fullfile(controllerPath(endsWith({controllerPath.name}, '_controller_log.txt')).folder, controllerPath(endsWith({controllerPath.name}, '_controller_log.txt')).name); -controller = readControllerLogs(controllerPath); -rpIdx = startsWith(controller.message, "Iteration duration: "); -s = split(controller.message(rpIdx), "Iteration duration: "); -s = split(s(:, 2), ' s'); -s = duration(strcat("00:", s(:, 1)), "InputFormat", "mm:ss.SSS"); -s.Format = "mm:ss.SSS"; -fprintf("Minimum command spacing: %2.3f seconds\n", seconds(min(s))); -fprintf("Maximum command spacing: %2.3f seconds\n", seconds(max(s))); -fprintf("Mean command spacing: %2.3f seconds\n", seconds(mean(s))); -fprintf("Median command spacing: %2.3f seconds\n", seconds(median(s))); -if seconds(min(s)) < 4 - warning("Minimum command spacing %2.3f questionably short for AERPAW", seconds(min(s))); -end +% Check timeline in controller logs +controller = controllerAnalysis(resultsPath); % Plot GPS logged data and scenario information (domain, objective, obstacles) seaToGroundLevel = 110; % measured approximately from USGS national map viewer @@ -54,7 +37,7 @@ for ii = 1:size(agents, 1) collisionGeometry = spherical; collisionGeometry = collisionGeometry.initialize(params.initialPositions((((ii - 1) * 3) + 1):(ii * 3)), params.collisionRadius(ii), REGION_TYPE.COLLISION, sprintf("Agent %d collision geometry", ii)); - agents{ii} = agents{ii}.initialize(params.initialPositions((((ii - 1) * 3) + 1):(ii * 3)), collisionGeometry, sensorModel, params.comRange(ii), params.maxIter, params.initialStepSize, sprintf("Agent %d", ii), plotCommsGeometry); + agents{ii} = agents{ii}.initialize(params.initialPositions((((ii - 1) * 3) + 1):(ii * 3)), collisionGeometry, sensorModel, params.comRange(ii), params.maxIter, params.initialStepSize, 5.0, sprintf("Agent %d", ii), plotCommsGeometry); end % Create obstacles @@ -81,9 +64,12 @@ copyobj(sim.f.Children, comparison); % Plot trajectories on top for ii = 1:size(G, 1) + gpsTimes = G{ii}.Timestamp; + gpsTimes.TimeZone = ''; + inRange = gpsTimes >= controller.timestamp(1) & gpsTimes <= controller.timestamp(end); for jj = 1:size(sim.spatialPlotIndices, 2) hold(comparison.Children.Children(sim.spatialPlotIndices(jj)), "on"); - plot3(comparison.Children(1).Children(sim.spatialPlotIndices(jj)), G{ii}.East, G{ii}.North, G{ii}.Up + seaToGroundLevel, 'Color', 'r', 'LineWidth', 1); + plot3(comparison.Children(1).Children(sim.spatialPlotIndices(jj)), G{ii}.East(inRange), G{ii}.North(inRange), G{ii}.Up(inRange) + seaToGroundLevel, 'Color', 'r', 'LineWidth', 1); hold(comparison.Children.Children(sim.spatialPlotIndices(jj)), "off"); end end \ No newline at end of file diff --git a/resources/project/cCclYJTOop6jkdZsItlf7iNuov4/tmG0S9kw2cno3kVq1hMKT8-rXXkd.xml b/resources/project/cCclYJTOop6jkdZsItlf7iNuov4/tmG0S9kw2cno3kVq1hMKT8-rXXkd.xml new file mode 100644 index 0000000..99772b4 --- /dev/null +++ b/resources/project/cCclYJTOop6jkdZsItlf7iNuov4/tmG0S9kw2cno3kVq1hMKT8-rXXkd.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/resources/project/cCclYJTOop6jkdZsItlf7iNuov4/tmG0S9kw2cno3kVq1hMKT8-rXXkp.xml b/resources/project/cCclYJTOop6jkdZsItlf7iNuov4/tmG0S9kw2cno3kVq1hMKT8-rXXkp.xml new file mode 100644 index 0000000..ae0264a --- /dev/null +++ b/resources/project/cCclYJTOop6jkdZsItlf7iNuov4/tmG0S9kw2cno3kVq1hMKT8-rXXkp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/test/parametricTestSuite.m b/test/parametricTestSuite.m index 273030c..5db14a8 100644 --- a/test/parametricTestSuite.m +++ b/test/parametricTestSuite.m @@ -47,7 +47,7 @@ classdef parametricTestSuite < matlab.unittest.TestCase collisionGeometry = spherical; collisionGeometry = collisionGeometry.initialize(params.initialPositions((((ii - 1) * 3) + 1):(ii * 3)), params.collisionRadius(ii), REGION_TYPE.COLLISION, sprintf("Agent %d collision geometry", ii)); - agents{ii} = agents{ii}.initialize(params.initialPositions((((ii - 1) * 3) + 1):(ii * 3)), collisionGeometry, sensorModel, params.comRange(ii), params.maxIter, params.initialStepSize, sprintf("Agent %d", ii), tc.plotCommsGeometry); + agents{ii} = agents{ii}.initialize(params.initialPositions((((ii - 1) * 3) + 1):(ii * 3)), collisionGeometry, sensorModel, params.comRange(ii), params.maxIter, params.initialStepSize, 5.0, sprintf("Agent %d", ii), tc.plotCommsGeometry); end % Create obstacles @@ -106,7 +106,7 @@ classdef parametricTestSuite < matlab.unittest.TestCase % Initialize agent collisionGeometry = collisionGeometry.initialize(agentPos, params.collisionRadius(ii, 1), REGION_TYPE.COLLISION, "Agent 1 Collision Region"); - agents{1} = agents{1}.initialize(agentPos, collisionGeometry, sensorModel, params.comRange(ii, 1), params.maxIter(ii), params.initialStepSize(ii), "Agent 1", tc.plotCommsGeometry); + agents{1} = agents{1}.initialize(agentPos, collisionGeometry, sensorModel, params.comRange(ii, 1), params.maxIter(ii), params.initialStepSize(ii), 5.0, "Agent 1", tc.plotCommsGeometry); % Set up remaining agents in random (valid) locations for jj = 2:size(agents, 1) @@ -148,7 +148,7 @@ classdef parametricTestSuite < matlab.unittest.TestCase % Initialize agent collisionGeometry = collisionGeometry.initialize(agentPos, params.collisionRadius(ii, jj), REGION_TYPE.COLLISION, sprintf("Agent %d Collision Region", jj)); - agents{jj} = agents{jj}.initialize(agentPos, collisionGeometry, sensorModel, params.comRange(ii, jj), params.maxIter(ii), params.initialStepSize(ii), sprintf("Agent %d", jj), tc.plotCommsGeometry); + agents{jj} = agents{jj}.initialize(agentPos, collisionGeometry, sensorModel, params.comRange(ii, jj), params.maxIter(ii), params.initialStepSize(ii), 5.0, sprintf("Agent %d", jj), tc.plotCommsGeometry); end % randomly shuffle agents to make the network more interesting (probably) diff --git a/test/test_miSim.m b/test/test_miSim.m index 22bd52b..e36d117 100644 --- a/test/test_miSim.m +++ b/test/test_miSim.m @@ -31,6 +31,7 @@ classdef test_miSim < matlab.unittest.TestCase % Agents initialStepSize = 0.2; % gradient ascent step size at the first iteration. Decreases linearly to 0 based on maxIter. + initialMaxAngleStepSize = 5; % angular step size (degrees) for tilt/azimuth gradient ascent per timestep. minAgents = 3; % Minimum number of agents to be randomly generated maxAgents = 4; % Maximum number of agents to be randomly generated useDoubleIntegrator = false; @@ -55,6 +56,7 @@ classdef test_miSim < matlab.unittest.TestCase % Communications useFixedTopology = false; + optimizeSensorPointing = false; minCommsRange = 3; % Minimum randomly generated collision geometry size maxCommsRange = 5; % Maximum randomly generated collision geometry size commsRanges = NaN; @@ -173,7 +175,7 @@ classdef test_miSim < matlab.unittest.TestCase tc.sensor = tc.sensor.initialize(tc.alphaDistMin + rand * (tc.alphaDistMax - tc.alphaDistMin), tc.betaDistMin + rand * (tc.betaDistMax - tc.betaDistMin), tc.alphaTiltMin + rand * (tc.alphaTiltMax - tc.alphaTiltMin), tc.betaTiltMin + rand * (tc.betaTiltMax - tc.betaTiltMin)); % Initialize candidate agent - newAgent = tc.agents{ii}.initialize(candidatePos, candidateGeometry, tc.sensor, tc.commsRanges(ii), tc.maxIter, tc.initialStepSize); + newAgent = tc.agents{ii}.initialize(candidatePos, candidateGeometry, tc.sensor, tc.commsRanges(ii), tc.maxIter, tc.initialStepSize, tc.initialMaxAngleStepSize); % Make sure candidate agent doesn't collide with % domain @@ -227,7 +229,7 @@ classdef test_miSim < matlab.unittest.TestCase end % Initialize the simulation - tc.testClass = tc.testClass.initialize(tc.domain, tc.agents, tc.barrierGain, tc.barrierExponent, tc.minAlt, tc.timestep, tc.maxIter, tc.obstacles, tc.makePlots, tc.makeVideo, tc.useDoubleIntegrator, tc.dampingCoeff, tc.useFixedTopology); + tc.testClass = tc.testClass.initialize(tc.domain, tc.agents, tc.barrierGain, tc.barrierExponent, tc.minAlt, tc.timestep, tc.maxIter, tc.obstacles, tc.makePlots, tc.makeVideo, tc.useDoubleIntegrator, tc.dampingCoeff, tc.useFixedTopology, tc.optimizeSensorPointing); end function miSim_run(tc) % randomly create obstacles @@ -312,7 +314,7 @@ classdef test_miSim < matlab.unittest.TestCase tc.sensor = tc.sensor.initialize(tc.alphaDistMin + rand * (tc.alphaDistMax - tc.alphaDistMin), tc.betaDistMin + rand * (tc.betaDistMax - tc.betaDistMin), tc.alphaTiltMin + rand * (tc.alphaTiltMax - tc.alphaTiltMin), tc.betaTiltMin + rand * (tc.betaTiltMax - tc.betaTiltMin)); % Initialize candidate agent - newAgent = tc.agents{ii}.initialize(candidatePos, candidateGeometry, tc.sensor, tc.commsRanges(ii), tc.maxIter, tc.initialStepSize); + newAgent = tc.agents{ii}.initialize(candidatePos, candidateGeometry, tc.sensor, tc.commsRanges(ii), tc.maxIter, tc.initialStepSize, tc.initialMaxAngleStepSize); % Make sure candidate agent doesn't collide with % domain @@ -366,7 +368,7 @@ classdef test_miSim < matlab.unittest.TestCase end % Initialize the simulation - tc.testClass = tc.testClass.initialize(tc.domain, tc.agents, tc.barrierGain, tc.barrierExponent, tc.minAlt, tc.timestep, tc.maxIter, tc.obstacles, tc.makePlots, tc.makeVideo, tc.useDoubleIntegrator, tc.dampingCoeff, tc.useFixedTopology); + tc.testClass = tc.testClass.initialize(tc.domain, tc.agents, tc.barrierGain, tc.barrierExponent, tc.minAlt, tc.timestep, tc.maxIter, tc.obstacles, tc.makePlots, tc.makeVideo, tc.useDoubleIntegrator, tc.dampingCoeff, tc.useFixedTopology, tc.optimizeSensorPointing); % Write out initialization state tc.testClass.writeInits(); @@ -392,15 +394,15 @@ classdef test_miSim < matlab.unittest.TestCase % Initialize agents tc.commsRanges = 3 * d * ones(size(tc.agents)); - tc.agents{1} = tc.agents{1}.initialize(tc.domain.center + [d, 0, 0], geometry1, tc.sensor, tc.commsRanges(1), tc.maxIter, tc.initialStepSize); - tc.agents{2} = tc.agents{2}.initialize(tc.domain.center - [d, 0, 0], geometry2, tc.sensor, tc.commsRanges(2), tc.maxIter, tc.initialStepSize); - tc.agents{3} = tc.agents{3}.initialize(tc.domain.center - [0, d, 0], geometry3, tc.sensor, tc.commsRanges(3), tc.maxIter, tc.initialStepSize); + tc.agents{1} = tc.agents{1}.initialize(tc.domain.center + [d, 0, 0], geometry1, tc.sensor, tc.commsRanges(1), tc.maxIter, tc.initialStepSize, tc.initialMaxAngleStepSize); + tc.agents{2} = tc.agents{2}.initialize(tc.domain.center - [d, 0, 0], geometry2, tc.sensor, tc.commsRanges(2), tc.maxIter, tc.initialStepSize, tc.initialMaxAngleStepSize); + tc.agents{3} = tc.agents{3}.initialize(tc.domain.center - [0, d, 0], geometry3, tc.sensor, tc.commsRanges(3), tc.maxIter, tc.initialStepSize, tc.initialMaxAngleStepSize); % Initialize the simulation tc.obstacles = cell(0, 1); tc.makePlots = false; tc.makeVideo = false; - tc.testClass = tc.testClass.initialize(tc.domain, tc.agents, tc.barrierGain, tc.barrierExponent, tc.minAlt, tc.timestep, tc.maxIter, tc.obstacles, tc.makePlots, tc.makeVideo, tc.useDoubleIntegrator, tc.dampingCoeff, tc.useFixedTopology); + tc.testClass = tc.testClass.initialize(tc.domain, tc.agents, tc.barrierGain, tc.barrierExponent, tc.minAlt, tc.timestep, tc.maxIter, tc.obstacles, tc.makePlots, tc.makeVideo, tc.useDoubleIntegrator, tc.dampingCoeff, tc.useFixedTopology, tc.optimizeSensorPointing); centerIdx = floor(size(tc.testClass.partitioning, 1) / 2); tc.verifyEqual(tc.testClass.partitioning(centerIdx, centerIdx:(centerIdx + 2)), [2, 3, 1]); % all three near center @@ -419,13 +421,13 @@ classdef test_miSim < matlab.unittest.TestCase tc.sensor = tc.sensor.initialize(tc.minDimension / 2, 3, 20, 3); % Initialize agents - tc.agents{1} = tc.agents{1}.initialize([tc.domain.center(1:2), 3], geometry1, tc.sensor, tc.commsRanges(1), tc.maxIter, tc.initialStepSize); + tc.agents{1} = tc.agents{1}.initialize([tc.domain.center(1:2), 3], geometry1, tc.sensor, tc.commsRanges(1), tc.maxIter, tc.initialStepSize, tc.initialMaxAngleStepSize); % Initialize the simulation tc.obstacles = cell(0, 1); tc.makePlots = false; tc.makeVideo = false; - tc.testClass = tc.testClass.initialize(tc.domain, tc.agents, tc.barrierGain, tc.barrierExponent, tc.minAlt, tc.timestep, tc.maxIter, tc.obstacles, tc.makePlots, tc.makeVideo, tc.useDoubleIntegrator, tc.dampingCoeff, tc.useFixedTopology); + tc.testClass = tc.testClass.initialize(tc.domain, tc.agents, tc.barrierGain, tc.barrierExponent, tc.minAlt, tc.timestep, tc.maxIter, tc.obstacles, tc.makePlots, tc.makeVideo, tc.useDoubleIntegrator, tc.dampingCoeff, tc.useFixedTopology, tc.optimizeSensorPointing); close(tc.testClass.fPerf); tc.verifyEqual(unique(tc.testClass.partitioning), [0; 1]); @@ -449,11 +451,11 @@ classdef test_miSim < matlab.unittest.TestCase % Initialize agents tc.maxIter = 75; - tc.agents{1} = tc.agents{1}.initialize([tc.domain.center(1:2)-tc.domain.dimensions(1)/4, 3], geometry1, tc.sensor, tc.commsRanges(1), tc.maxIter, tc.initialStepSize); + tc.agents{1} = tc.agents{1}.initialize([tc.domain.center(1:2)-tc.domain.dimensions(1)/4, 3], geometry1, tc.sensor, tc.commsRanges(1), tc.maxIter, tc.initialStepSize, tc.initialMaxAngleStepSize); % Initialize the simulation tc.obstacles = cell(0, 1); - tc.testClass = tc.testClass.initialize(tc.domain, tc.agents, tc.barrierGain, tc.barrierExponent, tc.minAlt, tc.timestep, tc.maxIter, tc.obstacles, tc.makePlots, tc.makeVideo, tc.useDoubleIntegrator, tc.dampingCoeff, tc.useFixedTopology); + tc.testClass = tc.testClass.initialize(tc.domain, tc.agents, tc.barrierGain, tc.barrierExponent, tc.minAlt, tc.timestep, tc.maxIter, tc.obstacles, tc.makePlots, tc.makeVideo, tc.useDoubleIntegrator, tc.dampingCoeff, tc.useFixedTopology, tc.optimizeSensorPointing); % Run the simulation tc.testClass = tc.testClass.run(); @@ -476,11 +478,11 @@ classdef test_miSim < matlab.unittest.TestCase % Initialize agents tc.maxIter = 75; - tc.agents{1} = tc.agents{1}.initialize([tc.domain.center(1:2)-tc.domain.dimensions(1)/4, 3], geometry1, tc.sensor, tc.commsRanges(1), tc.maxIter, tc.initialStepSize); + tc.agents{1} = tc.agents{1}.initialize([tc.domain.center(1:2)-tc.domain.dimensions(1)/4, 3], geometry1, tc.sensor, tc.commsRanges(1), tc.maxIter, tc.initialStepSize, tc.initialMaxAngleStepSize); % Initialize the simulation tc.obstacles = cell(0, 1); - tc.testClass = tc.testClass.initialize(tc.domain, tc.agents, tc.barrierGain, tc.barrierExponent, tc.minAlt, tc.timestep, tc.maxIter, tc.obstacles, tc.makePlots, tc.makeVideo, tc.useDoubleIntegrator, tc.dampingCoeff, tc.useFixedTopology); + tc.testClass = tc.testClass.initialize(tc.domain, tc.agents, tc.barrierGain, tc.barrierExponent, tc.minAlt, tc.timestep, tc.maxIter, tc.obstacles, tc.makePlots, tc.makeVideo, tc.useDoubleIntegrator, tc.dampingCoeff, tc.useFixedTopology, tc.optimizeSensorPointing); % Run the simulation tc.testClass = tc.testClass.run(); @@ -504,18 +506,86 @@ classdef test_miSim < matlab.unittest.TestCase BW = 20e6; % Bandwidth (Hz) f_c = 2e9; % Center frequency (Hz) G_RX_dBi = 3; % Receiving Antenna Gain (dBi) + beamwidthExponent = 6; + lossExponent = 2; tc.sensor = rfSensor; - tc.sensor = tc.sensor.initialize(P_TX, BW, f_c, G_RX_dBi, 45, 45); + tc.sensor = tc.sensor.initialize(P_TX, BW, f_c, G_RX_dBi, beamwidthExponent, 45, 45, lossExponent); % Initialize agents tc.maxIter = 75; - tc.agents{1} = tc.agents{1}.initialize([tc.domain.center(1:2)-tc.domain.dimensions(1)/4, 3], geometry1, tc.sensor, tc.commsRanges(1), tc.maxIter, tc.initialStepSize); + tc.agents{1} = tc.agents{1}.initialize([tc.domain.center(1:2)-tc.domain.dimensions(1)/4, 3], geometry1, tc.sensor, tc.commsRanges(1), tc.maxIter, tc.initialStepSize, tc.initialMaxAngleStepSize); % Initialize the simulation tc.obstacles = cell(0, 1); tc.minAlt = 0.5; - tc.testClass = tc.testClass.initialize(tc.domain, tc.agents, tc.barrierGain, tc.barrierExponent, tc.minAlt, tc.timestep, tc.maxIter, tc.obstacles, tc.makePlots, tc.makeVideo, tc.useDoubleIntegrator, tc.dampingCoeff, tc.useFixedTopology); + tc.testClass = tc.testClass.initialize(tc.domain, tc.agents, tc.barrierGain, tc.barrierExponent, tc.minAlt, tc.timestep, tc.maxIter, tc.obstacles, tc.makePlots, tc.makeVideo, tc.useDoubleIntegrator, tc.dampingCoeff, tc.useFixedTopology, tc.optimizeSensorPointing); + + % Run the simulation + tc.testClass = tc.testClass.run(); + end + function test_single_agent_gradient_ascent_sensor_pointing(tc) + % make basic domain + tc.minDimension = 10; % domain size + tc.domain = tc.domain.initialize([zeros(1, 3);tc.minDimension* ones(1, 3)], REGION_TYPE.DOMAIN, "Domain"); + + % make basic sensing objective + tc.domain.objective = tc.domain.objective.initialize(objectiveFunctionWrapper([7, 6]), tc.domain, tc.discretizationStep, tc.protectedRange, 1e-6, [7, 6]); + + % Initialize agent collision geometry + tc.agents = {agent}; + geometry1 = spherical; + geometry1 = geometry1.initialize([tc.domain.center(1:2)-tc.domain.dimensions(1)/4, 3], tc.collisionRanges(1), REGION_TYPE.COLLISION); + + % Initialize agent sensor model with fixed parameters + tc.sensor = tc.sensor.initialize(tc.minDimension / 2, 3, 20, 3); + + % Initialize agents + tc.maxIter = 75; + tc.agents{1} = tc.agents{1}.initialize([tc.domain.center(1:2)-tc.domain.dimensions(1)/4, 3], geometry1, tc.sensor, tc.commsRanges(1), tc.maxIter, tc.initialStepSize, tc.initialMaxAngleStepSize); + + % Initialize the simulation + tc.optimizeSensorPointing = true; + tc.obstacles = cell(0, 1); + tc.testClass = tc.testClass.initialize(tc.domain, tc.agents, tc.barrierGain, tc.barrierExponent, tc.minAlt, tc.timestep, tc.maxIter, tc.obstacles, tc.makePlots, tc.makeVideo, tc.useDoubleIntegrator, tc.dampingCoeff, tc.useFixedTopology, tc.optimizeSensorPointing); + + % Run the simulation + tc.testClass = tc.testClass.run(); + end + function test_single_agent_gradient_ascent_rf_sensor_pointing(tc) + % make basic domain + tc.minDimension = 10; % domain size + tc.domain = tc.domain.initialize([zeros(1, 3);tc.minDimension* ones(1, 3)], REGION_TYPE.DOMAIN, "Domain"); + + % make basic sensing objective + minimumSINR = 50; % (dB) + tc.domain.objective = tc.domain.objective.initialize(objectiveFunctionWrapper([7, 6]), tc.domain, tc.discretizationStep, tc.protectedRange, minimumSINR, [7, 6]); + + % Initialize agent collision geometry + tc.agents = {agent}; + geometry1 = spherical; + geometry1 = geometry1.initialize([tc.domain.center(1:2)-tc.domain.dimensions(1)/4, 3], tc.collisionRanges(1), REGION_TYPE.COLLISION); + + % Initialize agent sensor model with fixed parameters + P_TX = 1e-3; % Transmit power (Watts) + BW = 20e6; % Bandwidth (Hz) + f_c = 2e9; % Center frequency (Hz) + G_RX_dBi = 3; % Receiving Antenna Gain (dBi) + beamwidthExponent = 6; + lossExponent = 2; + + tc.sensor = rfSensor; + tc.sensor = tc.sensor.initialize(P_TX, BW, f_c, G_RX_dBi, beamwidthExponent, 0, 0, lossExponent); + + % Initialize agents + tc.maxIter = 75; + tc.agents{1} = tc.agents{1}.initialize([tc.domain.center(1:2)-tc.domain.dimensions(1)/4, 3], geometry1, tc.sensor, tc.commsRanges(1), tc.maxIter, tc.initialStepSize, tc.initialMaxAngleStepSize); + + % Initialize the simulation + tc.optimizeSensorPointing = true; + tc.obstacles = cell(0, 1); + tc.minAlt = 0.5; + tc.testClass = tc.testClass.initialize(tc.domain, tc.agents, tc.barrierGain, tc.barrierExponent, tc.minAlt, tc.timestep, tc.maxIter, tc.obstacles, tc.makePlots, tc.makeVideo, tc.useDoubleIntegrator, tc.dampingCoeff, tc.useFixedTopology, tc.optimizeSensorPointing); % Run the simulation tc.testClass = tc.testClass.run(); @@ -546,12 +616,12 @@ classdef test_miSim < matlab.unittest.TestCase % Initialize agents tc.maxIter = 25; tc.commsRanges = 5 * ones(size(tc.agents)); - tc.agents{1} = tc.agents{1}.initialize(tc.domain.center + d, geometry1, tc.sensor, tc.commsRanges(1), tc.maxIter, tc.initialStepSize); - tc.agents{2} = tc.agents{2}.initialize(tc.domain.center - d, geometry2, tc.sensor, tc.commsRanges(2), tc.maxIter, tc.initialStepSize); + tc.agents{1} = tc.agents{1}.initialize(tc.domain.center + d, geometry1, tc.sensor, tc.commsRanges(1), tc.maxIter, tc.initialStepSize, tc.initialMaxAngleStepSize); + tc.agents{2} = tc.agents{2}.initialize(tc.domain.center - d, geometry2, tc.sensor, tc.commsRanges(2), tc.maxIter, tc.initialStepSize, tc.initialMaxAngleStepSize); % Initialize the simulation tc.obstacles = cell(0, 1); - tc.testClass = tc.testClass.initialize(tc.domain, tc.agents, tc.barrierGain, tc.barrierExponent, tc.minAlt, tc.timestep, tc.maxIter, tc.obstacles, tc.makePlots, tc.makeVideo, tc.useDoubleIntegrator, tc.dampingCoeff, tc.useFixedTopology); + tc.testClass = tc.testClass.initialize(tc.domain, tc.agents, tc.barrierGain, tc.barrierExponent, tc.minAlt, tc.timestep, tc.maxIter, tc.obstacles, tc.makePlots, tc.makeVideo, tc.useDoubleIntegrator, tc.dampingCoeff, tc.useFixedTopology, tc.optimizeSensorPointing); % Run the simulation tc.testClass.run(); @@ -593,11 +663,11 @@ classdef test_miSim < matlab.unittest.TestCase % Initialize agents tc.commsRanges = (2 * tc.collisionRanges(1) + obstacleLength) * 0.9 * ones(size(tc.agents)); % defined such that they cannot go around the obstacle on both sides - tc.agents{1} = tc.agents{1}.initialize(tc.domain.center - d + [0, tc.collisionRanges(1) * 1.1 - yOffset, 0], geometry1, tc.sensor, tc.commsRanges(1), tc.maxIter, tc.initialStepSize); - tc.agents{2} = tc.agents{2}.initialize(tc.domain.center - d - [0, tc.collisionRanges(2) *1.1 + yOffset, 0], geometry2, tc.sensor, tc.commsRanges(2), tc.maxIter, tc.initialStepSize); + tc.agents{1} = tc.agents{1}.initialize(tc.domain.center - d + [0, tc.collisionRanges(1) * 1.1 - yOffset, 0], geometry1, tc.sensor, tc.commsRanges(1), tc.maxIter, tc.initialStepSize, tc.initialMaxAngleStepSize); + tc.agents{2} = tc.agents{2}.initialize(tc.domain.center - d - [0, tc.collisionRanges(2) *1.1 + yOffset, 0], geometry2, tc.sensor, tc.commsRanges(2), tc.maxIter, tc.initialStepSize, tc.initialMaxAngleStepSize); % Initialize the simulation - tc.testClass = tc.testClass.initialize(tc.domain, tc.agents, tc.barrierGain, tc.barrierExponent, tc.minAlt, tc.timestep, tc.maxIter, tc.obstacles, tc.makePlots, tc.makeVideo, tc.useDoubleIntegrator, tc.dampingCoeff, tc.useFixedTopology); + tc.testClass = tc.testClass.initialize(tc.domain, tc.agents, tc.barrierGain, tc.barrierExponent, tc.minAlt, tc.timestep, tc.maxIter, tc.obstacles, tc.makePlots, tc.makeVideo, tc.useDoubleIntegrator, tc.dampingCoeff, tc.useFixedTopology, tc.optimizeSensorPointing); % Run the simulation tc.testClass.run(); @@ -633,11 +703,11 @@ classdef test_miSim < matlab.unittest.TestCase % Initialize agents tc.maxIter = 50; tc.commsRanges = 4 * ones(size(tc.agents)); % defined such that they cannot reach their objective without breaking connectivity - tc.agents{1} = tc.agents{1}.initialize(tc.domain.center + d, geometry1, tc.sensor, tc.commsRanges(1), tc.maxIter, tc.initialStepSize); - tc.agents{2} = tc.agents{2}.initialize(tc.domain.center - d, geometry2, tc.sensor, tc.commsRanges(2), tc.maxIter, tc.initialStepSize); + tc.agents{1} = tc.agents{1}.initialize(tc.domain.center + d, geometry1, tc.sensor, tc.commsRanges(1), tc.maxIter, tc.initialStepSize, tc.initialMaxAngleStepSize); + tc.agents{2} = tc.agents{2}.initialize(tc.domain.center - d, geometry2, tc.sensor, tc.commsRanges(2), tc.maxIter, tc.initialStepSize, tc.initialMaxAngleStepSize); % Initialize the simulation - tc.testClass = tc.testClass.initialize(tc.domain, tc.agents, tc.barrierGain, tc.barrierExponent, tc.minAlt, tc.timestep, tc.maxIter, tc.obstacles, tc.makePlots, tc.makeVideo, tc.useDoubleIntegrator, tc.dampingCoeff, tc.useFixedTopology); + tc.testClass = tc.testClass.initialize(tc.domain, tc.agents, tc.barrierGain, tc.barrierExponent, tc.minAlt, tc.timestep, tc.maxIter, tc.obstacles, tc.makePlots, tc.makeVideo, tc.useDoubleIntegrator, tc.dampingCoeff, tc.useFixedTopology, tc.optimizeSensorPointing); % Run the simulation tc.testClass = tc.testClass.run(); @@ -668,8 +738,8 @@ classdef test_miSim < matlab.unittest.TestCase % Initialize agents tc.maxIter = 125; tc.commsRanges = 5 * ones(size(tc.agents)); - tc.agents{1} = tc.agents{1}.initialize(tc.domain.center - [d, 0, 0], geometry1, tc.sensor, tc.commsRanges(1), tc.maxIter, tc.initialStepSize); - tc.agents{2} = tc.agents{2}.initialize(tc.domain.center - [0, d, 0], geometry2, tc.sensor, tc.commsRanges(2), tc.maxIter, tc.initialStepSize); + tc.agents{1} = tc.agents{1}.initialize(tc.domain.center - [d, 0, 0], geometry1, tc.sensor, tc.commsRanges(1), tc.maxIter, tc.initialStepSize, tc.initialMaxAngleStepSize); + tc.agents{2} = tc.agents{2}.initialize(tc.domain.center - [0, d, 0], geometry2, tc.sensor, tc.commsRanges(2), tc.maxIter, tc.initialStepSize, tc.initialMaxAngleStepSize); % Initialize obstacles obstacleLength = 1.5; @@ -680,7 +750,7 @@ classdef test_miSim < matlab.unittest.TestCase tc.minAlt = 0; tc.makePlots = false; tc.makeVideo = false; - tc.testClass = tc.testClass.initialize(tc.domain, tc.agents, tc.barrierGain, tc.barrierExponent, tc.minAlt, tc.timestep, tc.maxIter, tc.obstacles, tc.makePlots, tc.makeVideo, tc.useDoubleIntegrator, tc.dampingCoeff, tc.useFixedTopology); + tc.testClass = tc.testClass.initialize(tc.domain, tc.agents, tc.barrierGain, tc.barrierExponent, tc.minAlt, tc.timestep, tc.maxIter, tc.obstacles, tc.makePlots, tc.makeVideo, tc.useDoubleIntegrator, tc.dampingCoeff, tc.useFixedTopology, tc.optimizeSensorPointing); % Communications link should be established tc.assertEqual(tc.testClass.adjacency, logical(true(2))); @@ -715,17 +785,17 @@ classdef test_miSim < matlab.unittest.TestCase % Initialize agents tc.maxIter = 125; tc.commsRanges = ones(size(tc.agents)); - tc.agents{1} = tc.agents{1}.initialize(tc.domain.center + [d, 0, 0], geometry1, tc.sensor, tc.commsRanges(1), tc.maxIter, tc.initialStepSize); - tc.agents{2} = tc.agents{2}.initialize(tc.domain.center, geometry2, tc.sensor, tc.commsRanges(2), tc.maxIter, tc.initialStepSize); - tc.agents{3} = tc.agents{3}.initialize(tc.domain.center + [-d, d, 0], geometry3, tc.sensor, tc.commsRanges(3), tc.maxIter, tc.initialStepSize); - tc.agents{4} = tc.agents{4}.initialize(tc.domain.center + [-2*d, d, 0], geometry4, tc.sensor, tc.commsRanges(4), tc.maxIter, tc.initialStepSize); - tc.agents{5} = tc.agents{5}.initialize(tc.domain.center + [0, d, 0], geometry5, tc.sensor, tc.commsRanges(5), tc.maxIter, tc.initialStepSize); + tc.agents{1} = tc.agents{1}.initialize(tc.domain.center + [d, 0, 0], geometry1, tc.sensor, tc.commsRanges(1), tc.maxIter, tc.initialStepSize, tc.initialMaxAngleStepSize); + tc.agents{2} = tc.agents{2}.initialize(tc.domain.center, geometry2, tc.sensor, tc.commsRanges(2), tc.maxIter, tc.initialStepSize, tc.initialMaxAngleStepSize); + tc.agents{3} = tc.agents{3}.initialize(tc.domain.center + [-d, d, 0], geometry3, tc.sensor, tc.commsRanges(3), tc.maxIter, tc.initialStepSize, tc.initialMaxAngleStepSize); + tc.agents{4} = tc.agents{4}.initialize(tc.domain.center + [-2*d, d, 0], geometry4, tc.sensor, tc.commsRanges(4), tc.maxIter, tc.initialStepSize, tc.initialMaxAngleStepSize); + tc.agents{5} = tc.agents{5}.initialize(tc.domain.center + [0, d, 0], geometry5, tc.sensor, tc.commsRanges(5), tc.maxIter, tc.initialStepSize, tc.initialMaxAngleStepSize); % Initialize the simulation tc.minAlt = 0; tc.makePlots = false; tc.makeVideo = false; - tc.testClass = tc.testClass.initialize(tc.domain, tc.agents, tc.barrierGain, tc.barrierExponent, tc.minAlt, tc.timestep, tc.maxIter, tc.obstacles, tc.makePlots, tc.makeVideo, tc.useDoubleIntegrator, tc.dampingCoeff, tc.useFixedTopology); + tc.testClass = tc.testClass.initialize(tc.domain, tc.agents, tc.barrierGain, tc.barrierExponent, tc.minAlt, tc.timestep, tc.maxIter, tc.obstacles, tc.makePlots, tc.makeVideo, tc.useDoubleIntegrator, tc.dampingCoeff, tc.useFixedTopology, tc.optimizeSensorPointing); % Constraint adjacency matrix defined by LNA should be as follows tc.assertEqual(tc.testClass.constraintAdjacencyMatrix, logical( ... @@ -767,19 +837,19 @@ classdef test_miSim < matlab.unittest.TestCase % Initialize agents tc.maxIter = 125; tc.commsRanges = d * ones(size(tc.agents)); - tc.agents{1} = tc.agents{1}.initialize(tc.domain.center + [-0.9 * d/sqrt(2), 0.9 * d/sqrt(2), 0], geometry1, tc.sensor, tc.commsRanges(1), tc.maxIter, tc.initialStepSize); - tc.agents{2} = tc.agents{2}.initialize(tc.domain.center + [-0.5 * d, 0.25 * d, 0], geometry2, tc.sensor, tc.commsRanges(2), tc.maxIter, tc.initialStepSize); - tc.agents{3} = tc.agents{3}.initialize(tc.domain.center + [0.9 * d, 0, 0], geometry3, tc.sensor, tc.commsRanges(3), tc.maxIter, tc.initialStepSize); - tc.agents{4} = tc.agents{4}.initialize(tc.domain.center + [0.9 * d/sqrt(2), -0.9 * d/sqrt(2), 0], geometry4, tc.sensor, tc.commsRanges(4), tc.maxIter, tc.initialStepSize); - tc.agents{5} = tc.agents{5}.initialize(tc.domain.center + [0, 0.9 * d, 0], geometry5, tc.sensor, tc.commsRanges(5), tc.maxIter, tc.initialStepSize); - tc.agents{6} = tc.agents{6}.initialize(tc.domain.center, geometry6, tc.sensor, tc.commsRanges(6), tc.maxIter, tc.initialStepSize); - tc.agents{7} = tc.agents{7}.initialize(tc.domain.center + [d/2, d/2, 0], geometry7, tc.sensor, tc.commsRanges(7), tc.maxIter, tc.initialStepSize); + tc.agents{1} = tc.agents{1}.initialize(tc.domain.center + [-0.9 * d/sqrt(2), 0.9 * d/sqrt(2), 0], geometry1, tc.sensor, tc.commsRanges(1), tc.maxIter, tc.initialStepSize, tc.initialMaxAngleStepSize); + tc.agents{2} = tc.agents{2}.initialize(tc.domain.center + [-0.5 * d, 0.25 * d, 0], geometry2, tc.sensor, tc.commsRanges(2), tc.maxIter, tc.initialStepSize, tc.initialMaxAngleStepSize); + tc.agents{3} = tc.agents{3}.initialize(tc.domain.center + [0.9 * d, 0, 0], geometry3, tc.sensor, tc.commsRanges(3), tc.maxIter, tc.initialStepSize, tc.initialMaxAngleStepSize); + tc.agents{4} = tc.agents{4}.initialize(tc.domain.center + [0.9 * d/sqrt(2), -0.9 * d/sqrt(2), 0], geometry4, tc.sensor, tc.commsRanges(4), tc.maxIter, tc.initialStepSize, tc.initialMaxAngleStepSize); + tc.agents{5} = tc.agents{5}.initialize(tc.domain.center + [0, 0.9 * d, 0], geometry5, tc.sensor, tc.commsRanges(5), tc.maxIter, tc.initialStepSize, tc.initialMaxAngleStepSize); + tc.agents{6} = tc.agents{6}.initialize(tc.domain.center, geometry6, tc.sensor, tc.commsRanges(6), tc.maxIter, tc.initialStepSize, tc.initialMaxAngleStepSize); + tc.agents{7} = tc.agents{7}.initialize(tc.domain.center + [d/2, d/2, 0], geometry7, tc.sensor, tc.commsRanges(7), tc.maxIter, tc.initialStepSize, tc.initialMaxAngleStepSize); % Initialize the simulation tc.minAlt = 0; tc.makePlots = false; tc.makeVideo = false; - tc.testClass = tc.testClass.initialize(tc.domain, tc.agents, tc.barrierGain, tc.barrierExponent, tc.minAlt, tc.timestep, tc.maxIter, tc.obstacles, tc.makePlots, tc.makeVideo, tc.useDoubleIntegrator, tc.dampingCoeff, tc.useFixedTopology); + tc.testClass = tc.testClass.initialize(tc.domain, tc.agents, tc.barrierGain, tc.barrierExponent, tc.minAlt, tc.timestep, tc.maxIter, tc.obstacles, tc.makePlots, tc.makeVideo, tc.useDoubleIntegrator, tc.dampingCoeff, tc.useFixedTopology, tc.optimizeSensorPointing); % Constraint adjacency matrix defined by LNA should be as follows tc.assertEqual(tc.testClass.constraintAdjacencyMatrix, logical( ... @@ -859,7 +929,7 @@ classdef test_miSim < matlab.unittest.TestCase tc.alphaTiltMin + rand * (tc.alphaTiltMax - tc.alphaTiltMin), ... tc.betaTiltMin + rand * (tc.betaTiltMax - tc.betaTiltMin)); newAgent = agent; - newAgent = newAgent.initialize(candidatePos, geom, tc.sensor, tc.commsRanges(ii), tc.maxIter, tc.initialStepSize); + newAgent = newAgent.initialize(candidatePos, geom, tc.sensor, tc.commsRanges(ii), tc.maxIter, tc.initialStepSize, tc.initialMaxAngleStepSize); % Domain / obstacle / agent collision checks violation = false; @@ -894,7 +964,7 @@ classdef test_miSim < matlab.unittest.TestCase sim1 = miSim; sim1 = sim1.initialize(tc.domain, tc.agents, tc.barrierGain, tc.barrierExponent, ... tc.minAlt, tc.timestep, tc.maxIter, tc.obstacles, false, false, ... - tc.useDoubleIntegrator, tc.dampingCoeff, tc.useFixedTopology); + tc.useDoubleIntegrator, tc.dampingCoeff, tc.useFixedTopology, tc.optimizeSensorPointing); % Write inits and build file path sim1.writeInits(); From 030dd30c7d2017189b7bce41ace1bb3c5480b6b6 Mon Sep 17 00:00:00 2001 From: Kevin D Date: Thu, 7 May 2026 20:00:05 -0700 Subject: [PATCH 26/32] new SINR/beamwidth 3d plot --- @rfSensor/plot.m | 125 ++++++++++++++++++++++++++++++++++++ @rfSensor/plotPerformance.m | 1 + @rfSensor/rfSensor.m | 1 + test/test_rfSensor.m | 27 ++++++++ 4 files changed, 154 insertions(+) create mode 100644 @rfSensor/plot.m diff --git a/@rfSensor/plot.m b/@rfSensor/plot.m new file mode 100644 index 0000000..1c1fbd9 --- /dev/null +++ b/@rfSensor/plot.m @@ -0,0 +1,125 @@ +function f = plot(obj, altitude, otherSensorsPos, otherSensors) + arguments (Input) + obj (1, 1) {mustBeA(obj, "rfSensor")}; + altitude (1, 1) double; + otherSensorsPos (:, 3) double = NaN(0, 3); + otherSensors (:, 1) cell = cell(0, 1); + end + arguments (Output) + f (1, 1) {mustBeA(f, "matlab.ui.Figure")}; + end + + % Clear local caches so this visualization always uses its own grid + obj.rssCache = []; + for ii = 1:numel(otherSensors) + otherSensors{ii}.rssCache = []; + end + + % bias other sensors altitudes appropriately + otherSensorsPos = otherSensorsPos + [0, 0, altitude]; + + % Create grid on which to evalute SINR, SNR + agentPos = [0, 0, altitude]; + d = 10; + if ~isempty(otherSensorsPos) + d = max(otherSensorsPos(:, 3) * 0.55); + d = max(d, max(vecnorm(otherSensorsPos(:, 1:2), 2, 2)) * 1.25); + end + c = 0.1; + d = ceil(d / c) * c; + distances = -d:c:d; + [targetPosX, targetPosY] = meshgrid(distances, distances); + + % Compute SINR, SNR + [SINR, ~] = obj.sensorPerformance(agentPos, [targetPosX(:), targetPosY(:), zeros(size(targetPosX(:)))], otherSensorsPos, otherSensors); + SINR = reshape(SINR, size(targetPosX)); + + % normalize in linear scale + % SINR = 10.^(SINR/10); SINR = SINR ./ max(SINR(:)); SINR = 10 * log10(SINR); + + % Collect sensor positions and boresight parameters for overlay + sensorTilts = [obj.tilt; cellfun(@(s) s.tilt, otherSensors)]; + sensorAzimuths = [obj.azimuth; cellfun(@(s) s.azimuth, otherSensors)]; + tailScale = 0.5 * d; + + f = figure; + surf(targetPosX, targetPosY, zeros(size(targetPosX)), SINR, "EdgeColor", "none"); + axis(f.Children(1), "image"); + colormap(f.Children(1), "hot"); + title("Ground User SINR and -3 dB antenna gain regions"); + subtitle(sprintf("%d interfering source(s)", size(otherSensorsPos, 1))); + c = colorbar; + ylabel(c, "SINR (dB)"); + xlabel("X (m)"); + ylabel("Y (m)"); + hold(f.Children(2), "on"); + scatter3(0, 0, altitude, 100, 'ko', "LineWidth", 2); + scatter3(otherSensorsPos(:, 1), otherSensorsPos(:, 2), otherSensorsPos(:, 3), 100, "bx", "LineWidth", 2); + qSelf = quiver3(0, 0, altitude, ... + tailScale * sind(obj.tilt) * sind(obj.azimuth), ... + tailScale * sind(obj.tilt) * cosd(obj.azimuth), ... + -tailScale * cosd(obj.tilt), ... + 0, 'k', 'LineWidth', 1.5); + qSelf.MaxHeadSize = 0.75; + if ~isempty(otherSensors) + qOthers = quiver3(otherSensorsPos(:,1), otherSensorsPos(:,2), otherSensorsPos(:,3), ... + tailScale .* sind(sensorTilts(2:end)) .* sind(sensorAzimuths(2:end)), ... + tailScale .* sind(sensorTilts(2:end)) .* cosd(sensorAzimuths(2:end)), ... + -tailScale .* cosd(sensorTilts(2:end)), ... + 0, 'b', 'LineWidth', 1.5); + qOthers.MaxHeadSize = 0.75; + end + % Draw half-angle cones co-boresighted with each quiver arrow + N = 48; + phi = linspace(0, 2*pi, N); + [PHI, S] = meshgrid(phi, [0; 1]); % row 1 = apex (s=0), row 2 = base (s=1) + allSensors = [{obj}; otherSensors]; + allPos = [[0, 0, altitude]; otherSensorsPos]; + for ii = 1:numel(allSensors) + ha = allSensors{ii}.halfAngle(); + tlt = sensorTilts(ii); + az = sensorAzimuths(ii); + pos = allPos(ii, :); + % Cone length: enough that the axis tip is guaranteed below z=0 + coneLength = 1.1 * pos(3) / max(cosd(tlt), 0.1); + % Nadir cone mesh: apex at origin, base at z = -coneLength + cX = S .* coneLength .* tand(ha) .* cos(PHI); + cY = S .* coneLength .* tand(ha) .* sin(PHI); + cZ = -S .* coneLength; + % Rotate nadir → boresight (same convention as quiver arrows) + Ry = [cosd(tlt), 0, -sind(tlt); 0, 1, 0; sind(tlt), 0, cosd(tlt)]; + Rz = [sind(az), -cosd(az), 0; cosd(az), sind(az), 0; 0, 0, 1]; + R = Rz * Ry; + pts = R * [cX(:)'; cY(:)'; cZ(:)']; + cX = reshape(pts(1,:), size(cX)) + pos(1); + cY = reshape(pts(2,:), size(cY)) + pos(2); + cZ = reshape(pts(3,:), size(cZ)) + pos(3); + if ii == 1 + fc = [0, 0, 0]; + else + fc = [0, 0, 1]; + end + surf(cX, cY, cZ, "FaceColor", fc, "FaceAlpha", 0.15, "EdgeColor", "none"); + + % Conic section: intersect each cone generator with z=0 + b_vec = R * [0; 0; -1]; + u_vec = R * [1; 0; 0]; + v_vec = R * [0; 1; 0]; + phi_sec = linspace(0, 2*pi, 720)'; + dirs = cosd(ha) .* b_vec' + sind(ha) .* (cos(phi_sec) .* u_vec' + sin(phi_sec) .* v_vec'); + t_sec = -pos(3) ./ dirs(:, 3); + t_sec(t_sec <= 0) = NaN; + sx = pos(1) + t_sec .* dirs(:, 1); + sy = pos(2) + t_sec .* dirs(:, 2); + plot3(sx, sy, zeros(size(sx)), "Color", fc, "LineWidth", 2); + end + clim(f.Children(2), [min(SINR(:)), max(SINR(:))]); + xlim(f.Children(2), [-d, d]); + ylim(f.Children(2), [-d, d]); + hold(f.Children(2), "off"); + zlim([0, Inf]); + + + + +end \ No newline at end of file diff --git a/@rfSensor/plotPerformance.m b/@rfSensor/plotPerformance.m index f36dcc1..1c4d441 100644 --- a/@rfSensor/plotPerformance.m +++ b/@rfSensor/plotPerformance.m @@ -15,6 +15,7 @@ function f = plotPerformance(obj, altitude, otherSensorsPos, otherSensors) otherSensors{ii}.rssCache = []; end + % bias other sensors altitudes appropriately otherSensorsPos = otherSensorsPos + [0, 0, altitude]; % Create grid on which to evalute SINR, SNR diff --git a/@rfSensor/rfSensor.m b/@rfSensor/rfSensor.m index 8417303..37f8ecb 100644 --- a/@rfSensor/rfSensor.m +++ b/@rfSensor/rfSensor.m @@ -29,6 +29,7 @@ classdef rfSensor [value] = halfAngle(obj); % tilt angle (deg) at which sensor performance is halved [f] = plotParameters(obj); % debug, plot sensor response as a function of distance and tilt angle [f] = plotPerformance(obj, altitude, otherSensorsPos, otherSensors); % debug, plot SNR or SINR ground heatmap for a given geometry + [f] = plot(obj, altitude, otherSensorsPos, otherSensors); obj = clearRssCache(obj); end methods (Access = private) diff --git a/test/test_rfSensor.m b/test/test_rfSensor.m index cad8b2e..1c77e4c 100644 --- a/test/test_rfSensor.m +++ b/test/test_rfSensor.m @@ -139,5 +139,32 @@ classdef test_rfSensor < matlab.unittest.TestCase sensor2.plotPerformance(pos2(3), [pos1 - pos2; pos3 - pos2], {sensor1; sensor3}); sensor3.plotPerformance(pos3(3), [pos1 - pos3; pos2 - pos3], {sensor1; sensor2}); end + function plot_SINR_heterogenous_interferers_3d(tc) + P_TX = 1e-3; + BW = 20e6; + f_c = 2e9; + G_RX_dBi = 3; + altitude = 30; + beamwidthExponent = [50, 20, 200]; + lossExponent = 2; + + sensor1 = rfSensor; + sensor1 = sensor1.initialize(P_TX, 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); + sensor3 = rfSensor; + sensor3 = sensor3.initialize(P_TX, BW, f_c, G_RX_dBi, beamwidthExponent(3), 20, 200, lossExponent); + + pos1 = [0, 0, altitude]; + pos2 = [6, -4, altitude - 5]; + pos3 = [-2, 6, altitude + 10]; + + % Plot SINR from each UAV's perspective. + % otherSensorsPos for plotPerformance: XY = offset from calling sensor, Z = absolute_alt - calling_alt. + % This is exactly posOther - posSelf for each row. + sensor1.plot(pos1(3), [pos2 - pos1; pos3 - pos1], {sensor2; sensor3}); + sensor2.plot(pos2(3), [pos1 - pos2; pos3 - pos2], {sensor1; sensor3}); + sensor3.plot(pos3(3), [pos1 - pos3; pos2 - pos3], {sensor1; sensor2}); + end end end \ No newline at end of file From 78f9dcd579d71412a1f09f1aaa8035fb1c2dd8c7 Mon Sep 17 00:00:00 2001 From: Kevin D Date: Fri, 8 May 2026 13:07:03 -0700 Subject: [PATCH 27/32] full simulation with RF sensors --- @agent/agent.m | 4 +- @agent/partition.m | 21 +- @agent/run.m | 36 +++- @miSim/run.m | 11 +- @miSim/writeInits.m | 41 +++- @rfSensor/RSS.m | 27 ++- @rfSensor/computePointToPoints.m | 30 +-- @rfSensor/rfSensor.m | 6 +- @rfSensor/sensorPerformance.m | 13 +- .../3zY8iV4IlWXtpru_5KSo-mgBjoEd.xml | 6 + .../3zY8iV4IlWXtpru_5KSo-mgBjoEp.xml | 2 + test/test_miSim.m | 202 +++++++++++++++--- 12 files changed, 312 insertions(+), 87 deletions(-) create mode 100644 resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/3zY8iV4IlWXtpru_5KSo-mgBjoEd.xml create mode 100644 resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/3zY8iV4IlWXtpru_5KSo-mgBjoEp.xml diff --git a/@agent/agent.m b/@agent/agent.m index e8db7ed..6945d1c 100644 --- a/@agent/agent.m +++ b/@agent/agent.m @@ -50,8 +50,8 @@ classdef agent obj.commsGeometry = spherical; end [obj] = initialize(obj, pos, pan, tilt, collisionGeometry, sensorModel, guidanceModel, comRange, index, label); - [obj] = run(obj, domain, partitioning, t, index, agents); - [partitioning] = partition(obj, agents, objective) + [obj] = run(obj, domain, partitioning, t, index, useDoubleIntegrator, dampingCoeff, dt, optimizeSensorPointing, otherAgents); + [partitioning, agents] = partition(obj, agents, objective) [obj, f] = plot(obj, ind, f); updatePlots(obj); end diff --git a/@agent/partition.m b/@agent/partition.m index 1d0b32b..768f8b4 100644 --- a/@agent/partition.m +++ b/@agent/partition.m @@ -1,4 +1,4 @@ -function [partitioning] = partition(obj, agents, objective) +function [partitioning, agents] = partition(obj, agents, objective) arguments (Input) obj (1, 1) {mustBeA(obj, "agent")}; agents (:, 1) {mustBeA(agents, "cell")}; @@ -6,6 +6,7 @@ function [partitioning] = partition(obj, agents, objective) end arguments (Output) partitioning (:, :) double; + agents (:, 1) cell; end nAgents = size(agents, 1); @@ -18,8 +19,22 @@ function [partitioning] = partition(obj, agents, objective) % minimum threshold that must be exceeded for any assignment. agentPerf = zeros(nPoints, nAgents + 1); for aa = 1:nAgents - p = agents{aa}.sensorModel.sensorPerformance(agents{aa}.pos, ... - [objective.X(:), objective.Y(:), zeros(nPoints, 1)]); + if isa(agents{aa}.sensorModel, "sigmoidSensor") + p = agents{aa}.sensorModel.sensorPerformance(agents{aa}.pos, ... + [objective.X(:), objective.Y(:), zeros(nPoints, 1)]); + elseif isa(agents{aa}.sensorModel, "rfSensor") + otherSensorsIdx = [1:(aa - 1), (aa + 1):size(agents, 1)]; + otherSensors = agents(otherSensorsIdx); + otherSensorsPos = cell2mat(cellfun(@(x) x.pos, otherSensors, "UniformOutput", false)); + otherSensors = cellfun(@(x) x.sensorModel, otherSensors, "UniformOutput", false); + [p, ~, agents{aa}.sensorModel, otherSensors] = agents{aa}.sensorModel.sensorPerformance(agents{aa}.pos, ... + [objective.X(:), objective.Y(:), zeros(nPoints, 1)], otherSensorsPos, otherSensors); + for k = 1:numel(otherSensorsIdx) + agents{otherSensorsIdx(k)}.sensorModel = otherSensors{k}; + end + else + error("?"); + end agentPerf(:, aa) = p(:); end agentPerf(:, nAgents + 1) = objective.sensorPerformanceMinimum; diff --git a/@agent/run.m b/@agent/run.m index 5852520..df88036 100644 --- a/@agent/run.m +++ b/@agent/run.m @@ -1,4 +1,4 @@ -function obj = run(obj, domain, partitioning, timestepIndex, index, useDoubleIntegrator, dampingCoeff, dt, optimizeSensorPointing) +function obj = run(obj, domain, partitioning, timestepIndex, index, useDoubleIntegrator, dampingCoeff, dt, optimizeSensorPointing, otherAgents) arguments (Input) obj (1, 1) {mustBeA(obj, "agent")}; domain (1, 1) {mustBeGeometry}; @@ -9,6 +9,7 @@ function obj = run(obj, domain, partitioning, timestepIndex, index, useDoubleInt dampingCoeff (1, 1) double = 2.0; dt (1, 1) double = 1.0; optimizeSensorPointing (1, 1) logical = false; + otherAgents (:, 1) cell = cell(); end arguments (Output) obj (1, 1) {mustBeA(obj, "agent")}; @@ -33,6 +34,26 @@ function obj = run(obj, domain, partitioning, timestepIndex, index, useDoubleInt maskedX = domain.objective.X(partitionMask); maskedY = domain.objective.Y(partitionMask); + if isa(obj.sensorModel, "rfSensor") + % Extract other agents' sensor models and positions once, outside the delta loop. + % Mask the full-grid RSS caches (filled by partition()) down to this agent's + % partition subset so sensorPerformance can reuse them for all perturbations. + otherSensorsPos = cell2mat(cellfun(@(x) x.pos, otherAgents, "UniformOutput", false)); + otherSensors = cellfun(@(x) x.sensorModel, otherAgents, "UniformOutput", false); + partitionIndices = find(partitionMask); + for kk = 1:numel(otherSensors) + if ~isempty(otherSensors{kk}.rssCache) + otherSensors{kk}.rssCache = otherSensors{kk}.rssCache(partitionIndices); + end + end + % Pre-mask this agent's own full-grid cache to the partition subset. + % Used for ii==1 (current position, no perturbation) to avoid recomputing. + baseSensorModel = obj.sensorModel; + if ~isempty(obj.sensorModel.rssCache) + baseSensorModel.rssCache = obj.sensorModel.rssCache(partitionIndices); + end + end + if optimizeSensorPointing % Stash actual current sensor model tilt/azimuth before messing with it % in these following hypotheticals @@ -58,7 +79,18 @@ function obj = run(obj, domain, partitioning, timestepIndex, index, useDoubleInt end % Compute performance values on partition - sensorValues = obj.sensorModel.sensorPerformance(pos, [maskedX, maskedY, zeros(size(maskedX))]); % S_n(omega, P_n) on W_n + if isa(obj.sensorModel, "sigmoidSensor") + sensorValues = obj.sensorModel.sensorPerformance(pos, [maskedX, maskedY, zeros(size(maskedX))]); % S_n(omega, P_n) on W_n + elseif isa(obj.sensorModel, "rfSensor") + if ii == 1 + sensorModelForDelta = baseSensorModel; % reuse partition-step cache; no recompute needed + else + sensorModelForDelta = obj.sensorModel.clearRssCache; + end + [sensorValues, ~, ~, ~] = sensorModelForDelta.sensorPerformance(pos, [maskedX, maskedY, zeros(size(maskedX))], otherSensorsPos, otherSensors); + else + error("?"); + end % Rearrange data into image arrays F = NaN(size(partitionMask)); diff --git a/@miSim/run.m b/@miSim/run.m index 40ab031..a0f8329 100644 --- a/@miSim/run.m +++ b/@miSim/run.m @@ -25,9 +25,16 @@ function [obj] = run(obj) obj.validate(); end + % Clear RF sensor caches + if isa(obj.agents{1}.sensorModel, "rfSensor") + for ss = 1:size(obj.agents, 1) + obj.agents{ss}.sensorModel = obj.agents{ss}.sensorModel.clearRssCache; + end + end + % Update partitioning before moving (this one is strictly for % plotting purposes, the real partitioning is done by the agents) - obj.partitioning = obj.agents{1}.partition(obj.agents, obj.domain.objective); + [obj.partitioning, obj.agents] = obj.agents{1}.partition(obj.agents, obj.domain.objective); % Determine desired communications links if ~obj.useFixedTopology @@ -42,7 +49,7 @@ function [obj] = run(obj) % Moving % Iterate over agents to simulate their unconstrained motion for jj = 1:size(obj.agents, 1) - obj.agents{jj} = obj.agents{jj}.run(obj.domain, obj.partitioning, obj.timestepIndex, jj, obj.useDoubleIntegrator, obj.dampingCoeff, obj.timestep, obj.optimizeSensorPointing); + obj.agents{jj} = obj.agents{jj}.run(obj.domain, obj.partitioning, obj.timestepIndex, jj, obj.useDoubleIntegrator, obj.dampingCoeff, obj.timestep, obj.optimizeSensorPointing, obj.agents([1:(jj - 1), (jj + 1):size(obj.agents, 1)])); end % Adjust motion determined by unconstrained gradient ascent using diff --git a/@miSim/writeInits.m b/@miSim/writeInits.m index baab094..cb0ddcb 100644 --- a/@miSim/writeInits.m +++ b/@miSim/writeInits.m @@ -13,10 +13,39 @@ function writeInits(obj) % Collect agent parameters collisionRadii = cellfun(@(x) x.collisionGeometry.radius, obj.agents); - alphaDist = cellfun(@(x) x.sensorModel.alphaDist, obj.agents); - betaDist = cellfun(@(x) x.sensorModel.betaDist, obj.agents); - alphaTilt = cellfun(@(x) x.sensorModel.alphaTilt, obj.agents); - betaTilt = cellfun(@(x) x.sensorModel.betaTilt, obj.agents); + if isprop(obj.agents{1}.sensorModel, "alphaDist") + % sigmoidSensor parameters + alphaDist = cellfun(@(x) x.sensorModel.alphaDist, obj.agents); + betaDist = cellfun(@(x) x.sensorModel.betaDist, obj.agents); + alphaTilt = cellfun(@(x) x.sensorModel.alphaTilt, obj.agents); + betaTilt = cellfun(@(x) x.sensorModel.betaTilt, obj.agents); + + % others to zero + lossExponent = zeros(size(obj.agents)); + P_TX = zeros(size(obj.agents)); + BW = zeros(size(obj.agents)); + f_c = zeros(size(obj.agents)); + G_RX_dBi = zeros(size(obj.agents)); + beamwidthExponent = zeros(size(obj.agents)); + + elseif isprop(obj.agents{1}.sensorModel, "P_TX") + % rfSensor parameters + lossExponent = cellfun(@(x) x.sensorModel.lossExponent, obj.agents); + P_TX = cellfun(@(x) x.sensorModel.P_TX, obj.agents); + BW = cellfun(@(x) x.sensorModel.BW, obj.agents); + f_c = cellfun(@(x) x.sensorModel.f_c, obj.agents); + G_RX_dBi = cellfun(@(x) x.sensorModel.G_RX_dBi, obj.agents); + beamwidthExponent = cellfun(@(x) x.sensorModel.beamwidthExponent, obj.agents); + + % others to zero + alphaDist = zeros(size(obj.agents)); + betaDist = zeros(size(obj.agents)); + alphaTilt = zeros(size(obj.agents)); + betaTilt = zeros(size(obj.agents)); + end + % joint parameters + tilt = cellfun(@(x) x.sensorModel.tilt, obj.agents); + azimuth = cellfun(@(x) x.sensorModel.azimuth, obj.agents); comRanges = cellfun(@(x) x.commsGeometry.radius, obj.agents); initialStepSize = cellfun(@(x) x.initialStepSize, obj.agents); pos = cell2mat(cellfun(@(x) x.pos, obj.agents, 'UniformOutput', false)); @@ -30,7 +59,9 @@ function writeInits(obj) "barrierGain", obj.barrierGain, "barrierExponent", obj.barrierExponent, "numObstacles", numInputObs, ... "numAgents", size(obj.agents, 1), "collisionRadius", collisionRadii, "comRange", comRanges, ... "useDoubleIntegrator", obj.useDoubleIntegrator, "dampingCoeff", obj.dampingCoeff, "useFixedTopology", obj.useFixedTopology, ... - "alphaDist", alphaDist, "betaDist", betaDist, "alphaTilt", alphaTilt, "betaTilt", betaTilt, ... + "tilt", tilt, "azimuth", azimuth, ... % joint sensor parameters + "alphaDist", alphaDist, "betaDist", betaDist, "alphaTilt", alphaTilt, "betaTilt", betaTilt, ... % sigmoid sensor parameters + "lossExponent", lossExponent, "P_TX", P_TX, "BW", BW, "f_c", f_c, "G_RX_dBi", G_RX_dBi, "beamwidthExponent", beamwidthExponent, ... % RF sensor parameters ... % ^^^ PARAMETERS ^^^ | vvv STATES vvv "pos", pos, "objectivePos", obj.domain.objective.groundPos, "objectiveSigma", obj.domain.objective.objectiveSigma, ... "domainMin", obj.domain.minCorner, "domainMax", obj.domain.maxCorner, ... diff --git a/@rfSensor/RSS.m b/@rfSensor/RSS.m index ef05692..9d49d35 100644 --- a/@rfSensor/RSS.m +++ b/@rfSensor/RSS.m @@ -1,15 +1,24 @@ -function value = RSS(obj, d, t, a) +function value = RSS(obj, d, dx, dy, dz) arguments (Input) obj (1, 1) {mustBeA(obj, "rfSensor")}; - d (:, 1) double; % distance from agent to target - t (:, 1) double; % LOS tilt angle - a (:, 1) double; % LOS azimuth angle + d (:, 1) double; + dx (:, 1) double; + dy (:, 1) double; + dz (:, 1) double; end arguments (Output) value (:, 1) double end - assert(size(d, 1) == size(t, 1), "Mismatch in number of distances (%d) and tilts (%d) provided", size(d, 1), size(t, 1)); - - % RSS (dBm) = TX Power (dBm) + TX Antenna Gain (dBi) + RX Antenna Gain (dBi) - Path Loss (dB) - value = obj.P_TX_dBm + obj.transmitterGain(t, a) + obj.G_RX_dBi - obj.pathLoss(d); -end \ No newline at end of file + % Boresight unit vector: [st*sa, st*ca, -ct] + % Target direction unit vector: [dx, dy, dz] / d + % cos_theta = dot product of the two, computed without per-point trig. + st = sind(obj.tilt); + ct = cosd(obj.tilt); + sa = sind(obj.azimuth); + ca = cosd(obj.azimuth); + 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); +end diff --git a/@rfSensor/computePointToPoints.m b/@rfSensor/computePointToPoints.m index 529a656..40a8644 100644 --- a/@rfSensor/computePointToPoints.m +++ b/@rfSensor/computePointToPoints.m @@ -1,24 +1,6 @@ -function [d, t, a] = computePointToPoints(obj, agentPos, targetPos) - arguments (Input) - obj (1, 1) {mustBeA(obj, "rfSensor")}; - agentPos (1, 3) double; - targetPos (:, 3) double; - end - arguments (Output) - d (:, 1) double; - t (:, 1) double; - a (:, 1) double; - end - - % distance from sensor to target - d = vecnorm(agentPos - targetPos, 2, 2); - - % distance from sensor nadir to target nadir (i.e. distance ignoring altitude) - x = vecnorm(agentPos(1:2) - targetPos(:, 1:2), 2, 2); - - % tilt angle (degrees) (0 (nadir), 180 (zenith)) - t = (180 - atan2d(x, targetPos(:, 3) - agentPos(3))); - - % azimuth angle (degrees) (0 (+y) clockwise to 360) - a = mod(atan2d(targetPos(:,1) - agentPos(1), targetPos(:,2) - agentPos(2)), 360); -end \ No newline at end of file +function [d, dx, dy, dz] = computePointToPoints(~, agentPos, targetPos) + dx = targetPos(:,1) - agentPos(1); + dy = targetPos(:,2) - agentPos(2); + dz = targetPos(:,3) - agentPos(3); + d = sqrt(dx.^2 + dy.^2 + dz.^2); +end diff --git a/@rfSensor/rfSensor.m b/@rfSensor/rfSensor.m index 37f8ecb..8d6fb63 100644 --- a/@rfSensor/rfSensor.m +++ b/@rfSensor/rfSensor.m @@ -15,17 +15,17 @@ classdef rfSensor P_TX_dBm = NaN; % Transmit power (dBm) N = NaN; % Thermal noise % Cached state (per timestep) - rssCache (:,1) double = double.empty(0,1); % linear-scale RSS to last ground targets grid end properties (Access = public) tilt = NaN; % Antenna boresight tilt (deg): 0=nadir, 90=horizon azimuth = NaN; % Antenna boresight azimuth (deg): 0=+y, 90=+x, 180=-y, 270=-x + rssCache (:,1) double = double.empty(0,1); % linear-scale RSS to last ground targets grid end methods (Access = public) [obj] = initialize(obj, txPower, 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, t, a] = computePointToPoints(obj, agentPos, targetPos); + [d, dx, dy, dz] = computePointToPoints(obj, agentPos, targetPos); [value] = halfAngle(obj); % tilt angle (deg) at which sensor performance is halved [f] = plotParameters(obj); % debug, plot sensor response as a function of distance and tilt angle [f] = plotPerformance(obj, altitude, otherSensorsPos, otherSensors); % debug, plot SNR or SINR ground heatmap for a given geometry @@ -33,7 +33,7 @@ classdef rfSensor obj = clearRssCache(obj); end methods (Access = private) - x = RSS(obj, d, t, a); % Received signal strength (function of distance and tilt angle) + 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 L_FSPL_dB = pathLoss(obj, d); % Free space path loss for a given TX/RX pair end diff --git a/@rfSensor/sensorPerformance.m b/@rfSensor/sensorPerformance.m index 65783f4..6502c69 100644 --- a/@rfSensor/sensorPerformance.m +++ b/@rfSensor/sensorPerformance.m @@ -14,22 +14,21 @@ function [SINR, SNR, obj, otherSensors] = sensorPerformance(obj, agentPos, targe end assert(size(otherSensorsPos, 1) == size(otherSensors, 1), "Mismatch in number of other sensor positions (%d) and number of other sensors (%d) provided", size(otherSensorsPos, 1), size(otherSensors, 1)); - [d, t, a] = obj.computePointToPoints(agentPos, targetPos); - if isempty(obj.rssCache) - obj.rssCache = 1e-3 .* 10 .^ (0.1 .* obj.RSS(d, t, a)); % dBm → W + [d, dx, dy, dz] = obj.computePointToPoints(agentPos, targetPos); + obj.rssCache = 1e-3 .* 10 .^ (0.1 .* obj.RSS(d, dx, dy, dz)); % dBm → W end S = obj.rssCache; - I = zeros(size(d)); + I = zeros(size(S)); for ii = 1:size(otherSensors, 1) if isempty(otherSensors{ii}.rssCache) - [d_other, t_other, a_other] = otherSensors{ii}.computePointToPoints(otherSensorsPos(ii, 1:3), targetPos); - otherSensors{ii}.rssCache = 1e-3 .* 10 .^ (0.1 .* otherSensors{ii}.RSS(d_other, t_other, a_other)); % dBm → W + [d_o, dx_o, dy_o, dz_o] = otherSensors{ii}.computePointToPoints(otherSensorsPos(ii, 1:3), targetPos); + otherSensors{ii}.rssCache = 1e-3 .* 10 .^ (0.1 .* otherSensors{ii}.RSS(d_o, dx_o, dy_o, dz_o)); % dBm → W end I = I + otherSensors{ii}.rssCache; end SINR = 10*log10(S ./ (I + obj.N)); - SNR = 10*log10(S ./ obj.N); + SNR = 10*log10(S ./ obj.N); end diff --git a/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/3zY8iV4IlWXtpru_5KSo-mgBjoEd.xml b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/3zY8iV4IlWXtpru_5KSo-mgBjoEd.xml new file mode 100644 index 0000000..99772b4 --- /dev/null +++ b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/3zY8iV4IlWXtpru_5KSo-mgBjoEd.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/3zY8iV4IlWXtpru_5KSo-mgBjoEp.xml b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/3zY8iV4IlWXtpru_5KSo-mgBjoEp.xml new file mode 100644 index 0000000..e9ea0b9 --- /dev/null +++ b/resources/project/MoO8eFA7MNcwhsnIRzm_IDZfmpg/3zY8iV4IlWXtpru_5KSo-mgBjoEp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/test/test_miSim.m b/test/test_miSim.m index e36d117..0d0572c 100644 --- a/test/test_miSim.m +++ b/test/test_miSim.m @@ -44,6 +44,8 @@ classdef test_miSim < matlab.unittest.TestCase collisionRanges = NaN; % Sensing + sensor = sigmoidSensor; + % sigmoidSensor betaDistMin = 3; betaDistMax = 15; betaTiltMin = 3; @@ -52,7 +54,15 @@ classdef test_miSim < matlab.unittest.TestCase alphaDistMax = 3; alphaTiltMin = 15; % degrees alphaTiltMax = 30; % degrees - sensor = sigmoidSensor; + opticalPartitioningMin = 1e-6; + % rfSensor + P_TX = 1e-3; % Transmit power (Watts) + BW = 20e6; % Bandwidth (Hz) + f_c = 3e9; % Center frequency (Hz) + G_RX_dBi = 3; % Receiving Antenna Gain (dBi) + beamwidthExponent = 16; + lossExponent = 2; + sinrPartitioningMin = 50; % Communications useFixedTopology = false; @@ -231,6 +241,154 @@ classdef test_miSim < matlab.unittest.TestCase % Initialize the simulation tc.testClass = tc.testClass.initialize(tc.domain, tc.agents, tc.barrierGain, tc.barrierExponent, tc.minAlt, tc.timestep, tc.maxIter, tc.obstacles, tc.makePlots, tc.makeVideo, tc.useDoubleIntegrator, tc.dampingCoeff, tc.useFixedTopology, tc.optimizeSensorPointing); end + function miSim_run_rf_sensor(tc) + % randomly create obstacles + nGeom = tc.minNumObstacles + randi(tc.maxNumObstacles - tc.minNumObstacles); + tc.obstacles = cell(nGeom, 1); + + % Iterate over obstacles to initialize + for ii = 1:size(tc.obstacles, 1) + badCandidate = true; + while badCandidate + % Instantiate a rectangular prism obstacle inside the domain + tc.obstacles{ii} = rectangularPrism; + tc.obstacles{ii} = tc.obstacles{ii}.initializeRandom(REGION_TYPE.OBSTACLE, sprintf("Obstacle %d", ii), tc.minObstacleSize, tc.maxObstacleSize, tc.domain, tc.minAlt); + + % Check if the obstacle collides with an existing obstacle + if ~tc.obstacleCollisionCheck(tc.obstacles(1:(ii - 1)), tc.obstacles{ii}) + badCandidate = false; + end + end + end + + % Add agents individually, ensuring that each addition does not + % invalidate the initialization setup + for ii = 1:size(tc.agents, 1) + initInvalid = true; + while initInvalid + candidatePos = [tc.domain.objective.groundPos, 0]; + % Generate a random position for the agent based on + % existing agent positions + if ii == 1 + while agentsCrowdObjective(tc.domain.objective, candidatePos, mean(tc.domain.dimensions) / 2) + candidatePos = tc.domain.random(); + candidatePos(3) = min([tc.domain.maxCorner(3) * 0.95, tc.minAlt + rand * (tc.alphaDistMax * (1.1) - 0.5)]); % place agents at decent altitudes for sensing + end + else + candidatePos = tc.agents{randi(ii - 1)}.pos + sign(randn([1, 3])) .* (rand(1, 3) .* tc.commsRanges(ii)/sqrt(2)); + candidatePos(3) = min([tc.domain.maxCorner(3) * 0.95, tc.minAlt + rand * (tc.alphaDistMax * (1.1) - 0.5)]); % place agents at decent altitudes for sensing + end + + % Make sure that the candidate position is within the + % domain + if ~tc.domain.contains(candidatePos) + continue; + end + + % Make sure that the candidate position does not crowd + % the sensing objective and create boring scenarios + if agentsCrowdObjective(tc.domain.objective, candidatePos, mean(tc.domain.dimensions) / 2) + continue; + end + + % Make sure that there exist unobstructed lines of sight at + % appropriate ranges to form a connected communications + % graph between the agents + connections = false(1, ii - 1); + for jj = 1:(ii - 1) + if norm(tc.agents{jj}.pos - candidatePos) <= min(tc.commsRanges([ii, jj])) + % Check new agent position against all existing + % agent positions for communications range + connections(jj) = true; + for kk = 1:size(tc.obstacles, 1) + if tc.obstacles{kk}.containsLine(tc.agents{jj}.pos, candidatePos) + connections(jj) = false; + end + end + end + end + + % New agent must be connected to an existing agent to + % be valid + if ii ~= 1 && ~any(connections) + continue; + end + + % Initialize candidate agent collision geometry + % candidateGeometry = rectangularPrism; + % candidateGeometry = candidateGeometry.initialize([candidatePos - tc.collisionRanges(ii) * ones(1, 3); candidatePos + tc.collisionRanges(ii) * ones(1, 3)], REGION_TYPE.COLLISION); + candidateGeometry = spherical; + candidateGeometry = candidateGeometry.initialize(candidatePos, tc.collisionRanges(ii), REGION_TYPE.COLLISION); + + % 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); + + % Initialize candidate agent + newAgent = tc.agents{ii}.initialize(candidatePos, candidateGeometry, tc.sensor, tc.commsRanges(ii), tc.maxIter, tc.initialStepSize, tc.initialMaxAngleStepSize); + + % Make sure candidate agent doesn't collide with + % domain + violation = false; + for jj = 1:size(newAgent.collisionGeometry.vertices, 1) + % Check if collision geometry exits domain + if ~tc.domain.contains(newAgent.collisionGeometry.vertices(jj, 1:3)) + violation = true; + break; + end + end + if violation + continue; + end + + % Make sure candidate doesn't collide with obstacles + violation = false; + for kk = 1:size(tc.obstacles, 1) + if geometryIntersects(tc.obstacles{kk}, newAgent.collisionGeometry) + violation = true; + break; + end + end + if violation + continue; + end + + % Make sure candidate doesn't collide with existing + % agents + violation = false; + for kk = 1:(ii - 1) + if geometryIntersects(tc.agents{kk}.collisionGeometry, newAgent.collisionGeometry) + violation = true; + break; + end + end + + % Make sure candidate clears domain floor + if newAgent.pos(3) - newAgent.collisionGeometry.radius <= tc.minAlt + violation = true; + end + + if violation + continue; + end + + % Candidate agent is valid, store to pass in to sim + initInvalid = false; + tc.agents{ii} = newAgent; + end + end + + % Initialize the simulation + tc.optimizeSensorPointing = true; + tc.testClass = tc.testClass.initialize(tc.domain, tc.agents, tc.barrierGain, tc.barrierExponent, tc.minAlt, tc.timestep, tc.maxIter, tc.obstacles, tc.makePlots, tc.makeVideo, tc.useDoubleIntegrator, tc.dampingCoeff, tc.useFixedTopology, tc.optimizeSensorPointing); + + % Write out initialization state + tc.testClass.writeInits(); + + % Run simulation loop + tc.testClass = tc.testClass.run(); + end function miSim_run(tc) % randomly create obstacles nGeom = tc.minNumObstacles + randi(tc.maxNumObstacles - tc.minNumObstacles); @@ -439,7 +597,7 @@ classdef test_miSim < matlab.unittest.TestCase tc.domain = tc.domain.initialize([zeros(1, 3);tc.minDimension* ones(1, 3)], REGION_TYPE.DOMAIN, "Domain"); % make basic sensing objective - tc.domain.objective = tc.domain.objective.initialize(objectiveFunctionWrapper([7, 6]), tc.domain, tc.discretizationStep, tc.protectedRange, 1e-6, [7, 6]); + tc.domain.objective = tc.domain.objective.initialize(objectiveFunctionWrapper([7, 6]), tc.domain, tc.discretizationStep, tc.protectedRange, tc.opticalPartitioningMin, [7, 6]); % Initialize agent collision geometry tc.agents = {agent}; @@ -466,7 +624,7 @@ classdef test_miSim < matlab.unittest.TestCase tc.domain = tc.domain.initialize([zeros(1, 3);tc.minDimension* ones(1, 3)], REGION_TYPE.DOMAIN, "Domain"); % make basic sensing objective - tc.domain.objective = tc.domain.objective.initialize(objectiveFunctionWrapper([7, 6]), tc.domain, tc.discretizationStep, tc.protectedRange, 1e-6, [7, 6]); + tc.domain.objective = tc.domain.objective.initialize(objectiveFunctionWrapper([7, 6]), tc.domain, tc.discretizationStep, tc.protectedRange, tc.opticalPartitioningMin, [7, 6]); % Initialize agent collision geometry tc.agents = {agent}; @@ -493,24 +651,15 @@ classdef test_miSim < matlab.unittest.TestCase tc.domain = tc.domain.initialize([zeros(1, 3);tc.minDimension* ones(1, 3)], REGION_TYPE.DOMAIN, "Domain"); % make basic sensing objective - minimumSINR = 50; % (dB) - tc.domain.objective = tc.domain.objective.initialize(objectiveFunctionWrapper([7, 6]), tc.domain, tc.discretizationStep, tc.protectedRange, minimumSINR, [7, 6]); + tc.domain.objective = tc.domain.objective.initialize(objectiveFunctionWrapper([7, 6]), tc.domain, tc.discretizationStep, tc.protectedRange, tc.sinrPartitioningMin, [7, 6]); % Initialize agent collision geometry tc.agents = {agent}; geometry1 = spherical; geometry1 = geometry1.initialize([tc.domain.center(1:2)-tc.domain.dimensions(1)/4, 3], tc.collisionRanges(1), REGION_TYPE.COLLISION); - - % Initialize agent sensor model with fixed parameters - P_TX = 1e-3; % Transmit power (Watts) - BW = 20e6; % Bandwidth (Hz) - f_c = 2e9; % Center frequency (Hz) - G_RX_dBi = 3; % Receiving Antenna Gain (dBi) - beamwidthExponent = 6; - lossExponent = 2; tc.sensor = rfSensor; - tc.sensor = tc.sensor.initialize(P_TX, BW, f_c, G_RX_dBi, beamwidthExponent, 45, 45, lossExponent); + tc.sensor = tc.sensor.initialize(tc.P_TX, tc.BW, tc.f_c, tc.G_RX_dBi, tc.beamwidthExponent, 45, 45, tc.lossExponent); % Initialize agents tc.maxIter = 75; @@ -530,7 +679,7 @@ classdef test_miSim < matlab.unittest.TestCase tc.domain = tc.domain.initialize([zeros(1, 3);tc.minDimension* ones(1, 3)], REGION_TYPE.DOMAIN, "Domain"); % make basic sensing objective - tc.domain.objective = tc.domain.objective.initialize(objectiveFunctionWrapper([7, 6]), tc.domain, tc.discretizationStep, tc.protectedRange, 1e-6, [7, 6]); + tc.domain.objective = tc.domain.objective.initialize(objectiveFunctionWrapper([7, 6]), tc.domain, tc.discretizationStep, tc.protectedRange, tc.opticalPartitioningMin, [7, 6]); % Initialize agent collision geometry tc.agents = {agent}; @@ -558,24 +707,17 @@ classdef test_miSim < matlab.unittest.TestCase tc.domain = tc.domain.initialize([zeros(1, 3);tc.minDimension* ones(1, 3)], REGION_TYPE.DOMAIN, "Domain"); % make basic sensing objective - minimumSINR = 50; % (dB) - tc.domain.objective = tc.domain.objective.initialize(objectiveFunctionWrapper([7, 6]), tc.domain, tc.discretizationStep, tc.protectedRange, minimumSINR, [7, 6]); + tc.domain.objective = tc.domain.objective.initialize(objectiveFunctionWrapper([7, 6]), tc.domain, tc.discretizationStep, tc.protectedRange, tc.sinrPartitioningMin, [7, 6]); % Initialize agent collision geometry tc.agents = {agent}; geometry1 = spherical; geometry1 = geometry1.initialize([tc.domain.center(1:2)-tc.domain.dimensions(1)/4, 3], tc.collisionRanges(1), REGION_TYPE.COLLISION); - % Initialize agent sensor model with fixed parameters - P_TX = 1e-3; % Transmit power (Watts) - BW = 20e6; % Bandwidth (Hz) - f_c = 2e9; % Center frequency (Hz) - G_RX_dBi = 3; % Receiving Antenna Gain (dBi) - beamwidthExponent = 6; - lossExponent = 2; + % Initialize agent sensor model tc.sensor = rfSensor; - tc.sensor = tc.sensor.initialize(P_TX, BW, f_c, G_RX_dBi, beamwidthExponent, 0, 0, lossExponent); + tc.sensor = tc.sensor.initialize(tc.P_TX, tc.BW, tc.f_c, tc.G_RX_dBi, tc.beamwidthExponent, 0, 0, tc.lossExponent); % Initialize agents tc.maxIter = 75; @@ -599,7 +741,7 @@ classdef test_miSim < matlab.unittest.TestCase tc.domain = tc.domain.initialize([zeros(1, 3);tc.minDimension* ones(1, 3)], REGION_TYPE.DOMAIN, "Domain"); % make basic sensing objective - tc.domain.objective = tc.domain.objective.initialize(objectiveFunctionWrapper([3, 7]), tc.domain, tc.discretizationStep, tc.protectedRange, 1e-6, [3, 7]); + tc.domain.objective = tc.domain.objective.initialize(objectiveFunctionWrapper([3, 7]), tc.domain, tc.discretizationStep, tc.protectedRange, tc.opticalPartitioningMin, [3, 7]); % Initialize agent collision geometry tc.agents = {agent; agent}; @@ -637,7 +779,7 @@ classdef test_miSim < matlab.unittest.TestCase tc.domain = tc.domain.initialize([zeros(1, 3);tc.minDimension* ones(1, 3)], REGION_TYPE.DOMAIN, "Domain"); % make basic sensing objective - tc.domain.objective = tc.domain.objective.initialize(objectiveFunctionWrapper([8, 5.2195]), tc.domain, tc.discretizationStep, tc.protectedRange, 1e-6, [8, 5.2195]); + tc.domain.objective = tc.domain.objective.initialize(objectiveFunctionWrapper([8, 5.2195]), tc.domain, tc.discretizationStep, tc.protectedRange, tc.opticalPartitioningMin, [8, 5.2195]); % Initialize agent collision geometry tc.agents = {agent; agent;}; @@ -721,7 +863,7 @@ classdef test_miSim < matlab.unittest.TestCase tc.domain = tc.domain.initialize([zeros(1, 3); tc.minDimension* ones(1, 3)], REGION_TYPE.DOMAIN, "Domain"); % make basic sensing objective - tc.domain.objective = tc.domain.objective.initialize(objectiveFunctionWrapper([8, 5]), tc.domain, tc.discretizationStep, tc.protectedRange, 1e-6, [8, 5]); + tc.domain.objective = tc.domain.objective.initialize(objectiveFunctionWrapper([8, 5]), tc.domain, tc.discretizationStep, tc.protectedRange, tc.opticalPartitioningMin, [8, 5]); % Initialize agent collision geometry tc.agents = {agent; agent;}; @@ -766,7 +908,7 @@ classdef test_miSim < matlab.unittest.TestCase tc.domain = tc.domain.initialize([zeros(1, 3);tc.minDimension* ones(1, 3)], REGION_TYPE.DOMAIN, "Domain"); % make basic sensing objective - tc.domain.objective = tc.domain.objective.initialize(objectiveFunctionWrapper([8, 5]), tc.domain, tc.discretizationStep, tc.protectedRange, 1e-6, [8, 5]); + tc.domain.objective = tc.domain.objective.initialize(objectiveFunctionWrapper([8, 5]), tc.domain, tc.discretizationStep, tc.protectedRange, tc.opticalPartitioningMin, [8, 5]); % Initialize agent collision geometry tc.agents = {agent; agent; agent; agent; agent;}; @@ -816,7 +958,7 @@ classdef test_miSim < matlab.unittest.TestCase tc.domain = tc.domain.initialize([zeros(1, 3); tc.minDimension* ones(1, 3)], REGION_TYPE.DOMAIN, "Domain"); % make basic sensing objective - tc.domain.objective = tc.domain.objective.initialize(objectiveFunctionWrapper([8, 5]), tc.domain, tc.discretizationStep, tc.protectedRange, 1e-6, [8, 5]); + tc.domain.objective = tc.domain.objective.initialize(objectiveFunctionWrapper([8, 5]), tc.domain, tc.discretizationStep, tc.protectedRange, tc.opticalPartitioningMin, [8, 5]); % Initialize agent collision geometry tc.agents = {agent; agent; agent; agent; agent; agent; agent;}; From db6bcbb1514bfc312939c89a88898894367ad0ae Mon Sep 17 00:00:00 2001 From: Kevin D Date: Wed, 13 May 2026 21:06:26 -0700 Subject: [PATCH 28/32] testing related fixes --- @miSim/run.m | 8 ++++++++ util/objectiveFunctionWrapper.m | 3 +++ 2 files changed, 11 insertions(+) diff --git a/@miSim/run.m b/@miSim/run.m index a0f8329..b0517d7 100644 --- a/@miSim/run.m +++ b/@miSim/run.m @@ -10,7 +10,12 @@ function [obj] = run(obj) % Start video writer if obj.makeVideo v = obj.setupVideoWriter(); + drawnow; v.open(); + % Capture reference frame size; used to resize frames that deviate + % due to figure reflow during plot updates (e.g. in headless mode). + I_ref = getframe(obj.f); + videoFrameSize = [size(I_ref.cdata, 2), size(I_ref.cdata, 1)]; end end @@ -73,6 +78,9 @@ function [obj] = run(obj) % Write frame in to video if obj.makeVideo I = getframe(obj.f); + if size(I.cdata, 2) ~= videoFrameSize(1) || size(I.cdata, 1) ~= videoFrameSize(2) + I.cdata = imresize(I.cdata, [videoFrameSize(2), videoFrameSize(1)]); + end v.writeVideo(I); end end diff --git a/util/objectiveFunctionWrapper.m b/util/objectiveFunctionWrapper.m index 0a9de95..c7cd743 100644 --- a/util/objectiveFunctionWrapper.m +++ b/util/objectiveFunctionWrapper.m @@ -10,6 +10,9 @@ function f = objectiveFunctionWrapper(center, sigma) f (1, 1) {mustBeA(f, "function_handle")}; end + if size(sigma, 1) == 1 && size(center, 1) > 1 + sigma = repmat(sigma, size(center, 1), 1, 1); + end assert(size(center, 1) == size(sigma, 1)); f = @(x,y) sum(cell2mat(arrayfun(@(i) mvnpdf([x(:), y(:)], center(i,:), squeeze(sigma(i, :, :))), 1:size(center,1), "UniformOutput", false)), 2); end \ No newline at end of file From ac56d3fcd2e9a20dd213c7f93ee79de56302bc7e Mon Sep 17 00:00:00 2001 From: Kevin D Date: Sat, 16 May 2026 15:08:00 -0700 Subject: [PATCH 29/32] radio plot cleanup --- aerpaw/results/plotRadioLogs.m | 173 +++++++++++++++++++++++---------- aerpaw/results/readRadioLogs.m | 34 +++++++ aerpaw/results/timeBinMedian.m | 29 ++++++ test/test_miSim.m | 6 +- 4 files changed, 190 insertions(+), 52 deletions(-) create mode 100644 aerpaw/results/timeBinMedian.m diff --git a/aerpaw/results/plotRadioLogs.m b/aerpaw/results/plotRadioLogs.m index 7371d73..c53ddbe 100644 --- a/aerpaw/results/plotRadioLogs.m +++ b/aerpaw/results/plotRadioLogs.m @@ -43,12 +43,44 @@ function [f, fDist, R] = plotRadioLogs(resultsPath, G, tLim) metricNames = ["SNR", "Power", "Quality", "PathLoss", "NoiseFloor", "FreqOffset"]; yLabels = ["SNR (dB)", "Power (dB)", "Quality", "Path Loss (dB)", "Noise Floor (dB)", "Freq Offset (MHz)"]; + nMetrics = numel(metricNames); % --- Time-based figure --- f = figure; - tl = tiledlayout(numel(metricNames), 1, 'TileSpacing', 'compact', 'Padding', 'compact'); + tl = tiledlayout(nMetrics + 1, 1, 'TileSpacing', 'compact', 'Padding', 'compact'); - for mi = 1:numel(metricNames) + % Distance vs time tile (first) + ax = nexttile(tl); + hold(ax, 'on'); grid(ax, 'on'); + legendEntries = string.empty; + ci = 1; + if ~isempty(G) + for rxIdx = 1:nUAV + tbl = R{rxIdx}; + txIDs = unique(tbl.TxUAVID); + for ti = 1:numel(txIDs) + txID = txIDs(ti); + rows = tbl(tbl.TxUAVID == txID, :); + rows = rows(rows.Timestamp >= tLim(1) & rows.Timestamp <= tLim(2), :); + if isempty(rows), continue; end + [~, ia] = unique(rows.Timestamp); + [radioPt, dist] = pairDist(rows(ia, :), G); + if isempty(dist) || all(isnan(dist)), continue; end + valid = ~isnan(dist); + si = mod(ci - 1, numel(styles)) + 1; + plot(ax, datetime(radioPt(valid), 'ConvertFrom', 'posixtime'), dist(valid), ... + styles(si), 'Color', colors(ci, :), 'MarkerSize', 3, 'LineWidth', 0.5); + legendEntries(end+1) = sprintf("TX %d → RX %d", txID, rows.RxUAVID(1)); %#ok + ci = ci + 1; + end + end + end + ylabel(ax, 'Distance (m)'); + xlabel(ax, 'Time'); + legend(ax, legendEntries, 'Location', 'best'); + hold(ax, 'off'); + + for mi = 1:nMetrics ax = nexttile(tl); hold(ax, 'on'); grid(ax, 'on'); @@ -63,21 +95,30 @@ function [f, fDist, R] = plotRadioLogs(resultsPath, G, tLim) rows = tbl(tbl.TxUAVID == txID, :); rows = rows(rows.Timestamp >= tLim(1) & rows.Timestamp <= tLim(2), :); vals = rows.(metricNames(mi)); + valid = ~isnan(vals); + rows = rows(valid, :); + vals = vals(valid); - if isempty(rows) || all(isnan(vals)) + if isempty(rows) continue; end si = mod(ci - 1, numel(styles)) + 1; plot(ax, rows.Timestamp, vals, styles(si), ... - 'Color', colors(ci, :), 'MarkerSize', 3, 'LineWidth', 1); + 'Color', colors(ci, :), 'MarkerSize', 3, 'LineWidth', 0.5); legendEntries(end+1) = sprintf("TX %d → RX %d", txID, tbl.RxUAVID(1)); %#ok + + % Median per 1/3-second time bin + [t_med, v_med] = timeBinMedian(posixtime(rows.Timestamp), vals, 1/3); + plot(ax, datetime(t_med, 'ConvertFrom', 'posixtime'), v_med, '-', ... + 'Color', 'r', 'LineWidth', 2); + legendEntries(end+1) = sprintf("TX %d → RX %d (median)", txID, tbl.RxUAVID(1)); %#ok ci = ci + 1; end end ylabel(ax, yLabels(mi)); - if mi == numel(metricNames) + if mi == nMetrics xlabel(ax, 'Time'); end legend(ax, legendEntries, 'Location', 'best'); @@ -93,9 +134,38 @@ function [f, fDist, R] = plotRadioLogs(resultsPath, G, tLim) return; end - tl2 = tiledlayout(numel(metricNames), 1, 'TileSpacing', 'compact', 'Padding', 'compact'); + tl2 = tiledlayout(nMetrics + 1, 1, 'TileSpacing', 'compact', 'Padding', 'compact'); - for mi = 1:numel(metricNames) + % Distance vs time tile (first) + ax = nexttile(tl2); + hold(ax, 'on'); grid(ax, 'on'); + legendEntries = string.empty; + ci = 1; + for rxIdx = 1:nUAV + tbl = R{rxIdx}; + txIDs = unique(tbl.TxUAVID); + for ti = 1:numel(txIDs) + txID = txIDs(ti); + rows = tbl(tbl.TxUAVID == txID, :); + rows = rows(rows.Timestamp >= tLim(1) & rows.Timestamp <= tLim(2), :); + if isempty(rows), continue; end + [~, ia] = unique(rows.Timestamp); + [radioPt, dist] = pairDist(rows(ia, :), G); + if isempty(dist) || all(isnan(dist)), continue; end + valid = ~isnan(dist); + si = mod(ci - 1, numel(styles)) + 1; + plot(ax, datetime(radioPt(valid), 'ConvertFrom', 'posixtime'), dist(valid), ... + styles(si), 'Color', colors(ci, :), 'MarkerSize', 3, 'LineWidth', 0.5); + legendEntries(end+1) = sprintf("TX %d → RX %d", txID, rows.RxUAVID(1)); %#ok + ci = ci + 1; + end + end + ylabel(ax, 'Distance (m)'); + xlabel(ax, 'Time'); + legend(ax, legendEntries, 'Location', 'best'); + hold(ax, 'off'); + + for mi = 1:nMetrics ax = nexttile(tl2); hold(ax, 'on'); grid(ax, 'on'); @@ -119,61 +189,39 @@ function [f, fDist, R] = plotRadioLogs(resultsPath, G, tLim) end vals = rows.(metricNames(mi)); - if all(isnan(vals)) + valid = ~isnan(vals); + rows = rows(valid, :); + vals = vals(valid); + + if isempty(rows) continue; end - % Map 0-based UAV IDs to 1-based GPS cell indices - txGpsIdx = double(txID) + 1; - rxGpsIdx = double(rows.RxUAVID(1)) + 1; + [radioPt, dist] = pairDist(rows, G); + if isempty(dist) || all(isnan(dist)), continue; end - if txGpsIdx > numel(G) || rxGpsIdx > numel(G) - continue; - end - - Gtx = G{txGpsIdx}; - Grx = G{rxGpsIdx}; - - if ~ismember('East', Gtx.Properties.VariableNames) || ... - ~ismember('East', Grx.Properties.VariableNames) - continue; - end - - % Strip timezone before posixtime so radio and GPS timestamps - % are treated on the same scale (both are AERPAW wall-clock time) - txTs = Gtx.Timestamp; txTs.TimeZone = ''; - rxTs = Grx.Timestamp; rxTs.TimeZone = ''; - txPt = posixtime(txTs); - rxPt = posixtime(rxTs); - radioPt = posixtime(rows.Timestamp); - - % Interpolate GPS positions at radio measurement times. - % Exclude NaN ENU entries (outside algorithm flight range). - validTx = ~isnan(Gtx.East); - validRx = ~isnan(Grx.East); - - txE = interp1(txPt(validTx), Gtx.East(validTx), radioPt, 'linear', NaN); - txN = interp1(txPt(validTx), Gtx.North(validTx), radioPt, 'linear', NaN); - txU = interp1(txPt(validTx), Gtx.Up(validTx), radioPt, 'linear', NaN); - rxE = interp1(rxPt(validRx), Grx.East(validRx), radioPt, 'linear', NaN); - rxN = interp1(rxPt(validRx), Grx.North(validRx), radioPt, 'linear', NaN); - rxU = interp1(rxPt(validRx), Grx.Up(validRx), radioPt, 'linear', NaN); - - dist = vecnorm([txE - rxE, txN - rxN, txU - rxU], 2, 2); - - if all(isnan(dist)) - continue; - end + % Drop points where GPS interpolation returned NaN + validDist = ~isnan(dist); + rowTs = radioPt(validDist); + dist = dist(validDist); + vals = vals(validDist); si = mod(ci - 1, numel(styles)) + 1; scatter(ax, dist, vals, 9, colors(ci, :), strrep(styles(si), "-", ""), 'filled'); legendEntries(end+1) = sprintf("TX %d → RX %d", txID, rows.RxUAVID(1)); %#ok + + % Median per 1/3-second time bin, plotted against median distance + [~, dv_med] = timeBinMedian(rowTs, [dist, vals], 1/3); + [d_med, si_sort] = sort(dv_med(:, 1)); + v_med = dv_med(si_sort, 2); + plot(ax, d_med, v_med, '-', 'Color', 'r', 'LineWidth', 2); + legendEntries(end+1) = sprintf("TX %d → RX %d (median)", txID, rows.RxUAVID(1)); %#ok ci = ci + 1; end end ylabel(ax, yLabels(mi)); - if mi == numel(metricNames) + if mi == nMetrics xlabel(ax, 'Distance (m)'); end legend(ax, legendEntries, 'Location', 'best'); @@ -182,3 +230,30 @@ function [f, fDist, R] = plotRadioLogs(resultsPath, G, tLim) title(tl2, 'Radio Channel Metrics vs Distance'); end + + +function [radioPt, dist] = pairDist(rows, G) + % Interpolate GPS-based inter-UAV distance at each row's timestamp. + radioPt = []; dist = []; + txGpsIdx = double(rows.TxUAVID(1)) + 1; + rxGpsIdx = double(rows.RxUAVID(1)) + 1; + if txGpsIdx > numel(G) || rxGpsIdx > numel(G), return; end + Gtx = G{txGpsIdx}; + Grx = G{rxGpsIdx}; + if ~ismember('East', Gtx.Properties.VariableNames) || ... + ~ismember('East', Grx.Properties.VariableNames), return; end + txTs = Gtx.Timestamp; txTs.TimeZone = ''; + rxTs = Grx.Timestamp; rxTs.TimeZone = ''; + txPt = posixtime(txTs); + rxPt = posixtime(rxTs); + radioPt = posixtime(rows.Timestamp); + validTx = ~isnan(Gtx.East); + validRx = ~isnan(Grx.East); + txE = interp1(txPt(validTx), Gtx.East(validTx), radioPt, 'linear', NaN); + txN = interp1(txPt(validTx), Gtx.North(validTx), radioPt, 'linear', NaN); + txU = interp1(txPt(validTx), Gtx.Up(validTx), radioPt, 'linear', NaN); + rxE = interp1(rxPt(validRx), Grx.East(validRx), radioPt, 'linear', NaN); + rxN = interp1(rxPt(validRx), Grx.North(validRx), radioPt, 'linear', NaN); + rxU = interp1(rxPt(validRx), Grx.Up(validRx), radioPt, 'linear', NaN); + dist = vecnorm([txE - rxE, txN - rxN, txU - rxU], 2, 2); +end diff --git a/aerpaw/results/readRadioLogs.m b/aerpaw/results/readRadioLogs.m index a54079d..07c9007 100644 --- a/aerpaw/results/readRadioLogs.m +++ b/aerpaw/results/readRadioLogs.m @@ -70,6 +70,40 @@ function R = readRadioLogs(logPath) R = sortrows(R, "Timestamp"); + % Reconstruct per-measurement timestamps within GNURadio processing batches. + % The flowgraph accumulates one full PN sequence (4095 chips at samp_rate/sps) + % per measurement, but outputs the whole batch simultaneously with a single + % wall-clock timestamp. We reassign timestamps by counting backward from the + % batch processing time at the known PN period interval. + pn_period = 4095 / (2e6 / 16); % 32.76 ms per PN correlation period + + for txId = unique(R.TxUAVID)' + rows = find(R.TxUAVID == txId); + if numel(rows) < 2, continue; end + + dt = seconds(diff(R.Timestamp(rows))); + break_pos = [1; find(dt > 0.5) + 1]; + end_pos = [break_pos(2:end) - 1; numel(rows)]; + + for b = 1:numel(break_pos) + idx = rows(break_pos(b) : end_pos(b)); + batch_ts = posixtime(R.Timestamp(idx)); + t_ref = max(batch_ts); + + % Multiple metric files share the same processing timestamp for + % each PN period, so group by unique original timestamp rather + % than treating every row as a separate PN period. + [~, ~, group_id] = unique(batch_ts); + n_groups = max(group_id); + new_ts = t_ref - (n_groups - 1 : -1 : 0)' * pn_period; + + for g = 1:n_groups + R.Timestamp(idx(group_id == g)) = ... + datetime(new_ts(g), 'ConvertFrom', 'posixtime'); + end + end + end + % Remove rows during defined guard period between TDM shifts R(R.TxUAVID == -1, :) = []; diff --git a/aerpaw/results/timeBinMedian.m b/aerpaw/results/timeBinMedian.m new file mode 100644 index 0000000..f2972fa --- /dev/null +++ b/aerpaw/results/timeBinMedian.m @@ -0,0 +1,29 @@ +function [t_med, v_med] = timeBinMedian(t, v, binWidth) + % Compute median of each column of v within fixed-width time bins. + % + % t - (N,1) posixtime values + % v - (N,K) data matrix; one column per quantity + % binWidth - scalar bin width in seconds + % + % t_med - (B,1) median time of each non-empty bin + % v_med - (B,K) median of each column per non-empty bin + + edges = (floor(min(t) / binWidth) * binWidth) : binWidth : ... + (floor(max(t) / binWidth) * binWidth + binWidth); + bins = discretize(t, edges); + nBins = numel(edges) - 1; + K = size(v, 2); + + t_all = NaN(nBins, 1); + v_all = NaN(nBins, K); + for bi = 1:nBins + mask = bins == bi; + if ~any(mask), continue; end + t_all(bi) = median(t(mask)); + v_all(bi,:) = median(v(mask,:), 1); + end + + ok = ~isnan(t_all); + t_med = t_all(ok); + v_med = v_all(ok, :); +end diff --git a/test/test_miSim.m b/test/test_miSim.m index 0d0572c..0a517d3 100644 --- a/test/test_miSim.m +++ b/test/test_miSim.m @@ -9,8 +9,8 @@ classdef test_miSim < matlab.unittest.TestCase plotCommsGeometry = false; % disable plotting communications geometries % Sim - maxIter = 50; - timestep = 0.05; + maxIter = 250; + timestep = 0.1; % Domain domain = rectangularPrism; % domain geometry @@ -31,7 +31,7 @@ classdef test_miSim < matlab.unittest.TestCase % Agents initialStepSize = 0.2; % gradient ascent step size at the first iteration. Decreases linearly to 0 based on maxIter. - initialMaxAngleStepSize = 5; % angular step size (degrees) for tilt/azimuth gradient ascent per timestep. + initialMaxAngleStepSize = 0.1; % angular step size (degrees) for tilt/azimuth gradient ascent per timestep. minAgents = 3; % Minimum number of agents to be randomly generated maxAgents = 4; % Maximum number of agents to be randomly generated useDoubleIntegrator = false; From cb9debf976fa182d57d4a5fff9ffec128ddaa976 Mon Sep 17 00:00:00 2001 From: Kevin D Date: Thu, 21 May 2026 09:46:47 -0700 Subject: [PATCH 30/32] new experiment scenario definition CSVs --- aerpaw/config/scenario_columns.csv | 2 ++ aerpaw/config/scenario_lock.csv | 2 ++ aerpaw/config/scenario_two_around_wall.csv | 2 ++ aerpaw/config/scenario_walls.csv | 2 ++ test/parametricTestSuite.m | 10 ++++++++-- 5 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 aerpaw/config/scenario_columns.csv create mode 100644 aerpaw/config/scenario_lock.csv create mode 100644 aerpaw/config/scenario_two_around_wall.csv create mode 100644 aerpaw/config/scenario_walls.csv diff --git a/aerpaw/config/scenario_columns.csv b/aerpaw/config/scenario_columns.csv new file mode 100644 index 0000000..1e11351 --- /dev/null +++ b/aerpaw/config/scenario_columns.csv @@ -0,0 +1,2 @@ +timestep, maxIter, minAlt, discretizationStep, protectedRange, initialStepSize, barrierGain, barrierExponent, collisionRadius, comRange, alphaDist, betaDist, alphaTilt, betaTilt, domainMin, domainMax, objectivePos, objectiveVar, sensorPerformanceMinimum, initialPositions, numObstacles, obstacleMin, obstacleMax, useDoubleIntegrator, dampingCoeff, useFixedTopology +1, 50, 35.0, 0.1, 2.0, 6, 1, 1, "8.0, 8.0", "35.0, 35.0", "80.0, 50.0", "0.25, 1.0", "8.0, 25.0", "0.1, 0.02", "0.0, 0.0, 0.0", "100.0, 100.0, 100.0", "60.0, 80.0, 45.0, 70.0", "70, 15, 15, 20, 20, 15, 15, 70", 0.15, "10.0, 10.0, 50.0, 40.0, 15.0, 45.0", 8, "0.0, 30.0, 0.0, 42.0, 30.0, 0.0, 84.0, 30.0, 0.0, 13.0, 60.0, 0.0, 55.0, 60.0, 0.0, 0.0, 90, 0.0, 42.0, 90.0, 0.0, 84.0, 90.0, 0.0", "16.0, 40.0, 100.0, 58.0, 40.0, 100.0, 100.0, 40.0, 100.0, 29.0, 70.0, 100.0, 71.0, 70.0, 100.0, 16.0, 100.0, 100.0, 58.0, 100.0, 100.0, 100.0, 100.0, 100.0", 0, 2.0, 1 \ No newline at end of file diff --git a/aerpaw/config/scenario_lock.csv b/aerpaw/config/scenario_lock.csv new file mode 100644 index 0000000..3dbf3b4 --- /dev/null +++ b/aerpaw/config/scenario_lock.csv @@ -0,0 +1,2 @@ +timestep, maxIter, minAlt, discretizationStep, protectedRange, initialStepSize, barrierGain, barrierExponent, collisionRadius, comRange, alphaDist, betaDist, alphaTilt, betaTilt, domainMin, domainMax, objectivePos, objectiveVar, sensorPerformanceMinimum, initialPositions, numObstacles, obstacleMin, obstacleMax, useDoubleIntegrator, dampingCoeff, useFixedTopology +1, 65, 35.0, 0.1, 2.0, 6, 1, 1, "8.0, 8.0", "35.0, 35.0", "80.0, 50.0", "0.25, 1.0", "8.0, 25.0", "0.1, 0.02", "0.0, 0.0, 0.0", "100.0, 100.0, 100.0", "30.0, 80.0", "60, 20, 20, 30", 0.15, "65.0, 15.0, 65.0, 65.0, 15.0, 45.0", 3, "0.0, 25.0, 55.0, 40.0, 10.0, 0.0, 40.0, 45.0, 60.0", "100.0, 70.0, 60.0, 45.0, 80.0, 55.0, 100.0, 50.0, 100.0", 0, 2.0, 1 \ No newline at end of file diff --git a/aerpaw/config/scenario_two_around_wall.csv b/aerpaw/config/scenario_two_around_wall.csv new file mode 100644 index 0000000..f155cfc --- /dev/null +++ b/aerpaw/config/scenario_two_around_wall.csv @@ -0,0 +1,2 @@ +timestep, maxIter, minAlt, discretizationStep, protectedRange, initialStepSize, barrierGain, barrierExponent, collisionRadius, comRange, alphaDist, betaDist, alphaTilt, betaTilt, domainMin, domainMax, objectivePos, objectiveVar, sensorPerformanceMinimum, initialPositions, numObstacles, obstacleMin, obstacleMax, useDoubleIntegrator, dampingCoeff, useFixedTopology +1, 100, 35.0, 0.1, 2.0, 6, 1, 1, "8.0, 8.0", "35.0, 35.0", "80.0, 80.0", "0.25, 0.25", "8.0, 8.0", "0.1, 0.1", "0.0, 0.0, 0.0", "100.0, 100.0, 100.0", "66.6, 66.6", "55, 35, 35, 55", 0.15, "15.0, 15.0, 50.0, 40.0, 15.0, 50.0", 1, "0.0, 35.0, 0.0", "50, 40.0, 60", 1, 2.0, 1 \ No newline at end of file diff --git a/aerpaw/config/scenario_walls.csv b/aerpaw/config/scenario_walls.csv new file mode 100644 index 0000000..1dd65d4 --- /dev/null +++ b/aerpaw/config/scenario_walls.csv @@ -0,0 +1,2 @@ +timestep, maxIter, minAlt, discretizationStep, protectedRange, initialStepSize, barrierGain, barrierExponent, collisionRadius, comRange, alphaDist, betaDist, alphaTilt, betaTilt, domainMin, domainMax, objectivePos, objectiveVar, sensorPerformanceMinimum, initialPositions, numObstacles, obstacleMin, obstacleMax, useDoubleIntegrator, dampingCoeff, useFixedTopology +1, 65, 35.0, 0.1, 2.0, 6, 1, 1, "8.0, 8.0", "35.0, 35.0", "80.0, 50.0", "0.25, 1.0", "8.0, 25.0", "0.1, 0.02", "0.0, 0.0, 0.0", "100.0, 100.0, 100.0", "30.0, 80.0", "60, 20, 20, 30", 0.15, "90.0, 10.0, 50.0, 65.0, 15.0, 45.0", 4, "0.0, 30.0, 0.0, 70.0, 30.0, 0.0, 0.0, 60.0, 0.0, 55.0, 60.0, 0.0", "50.0, 40.0, 100.0, 100.0, 40.0, 100.0, 35.0, 70.0, 100.0, 100.0, 70.0, 100.0", 0, 2.0, 1 \ No newline at end of file diff --git a/test/parametricTestSuite.m b/test/parametricTestSuite.m index 5db14a8..e7b0cc1 100644 --- a/test/parametricTestSuite.m +++ b/test/parametricTestSuite.m @@ -34,8 +34,14 @@ classdef parametricTestSuite < matlab.unittest.TestCase % Define scenario according to CSV specification tc.domain = tc.domain.initialize([params.domainMin; params.domainMax], REGION_TYPE.DOMAIN, "Domain"); - objectiveSigma = reshape(params.objectiveVar, [1 2 2]); - tc.domain.objective = tc.domain.objective.initialize(objectiveFunctionWrapper(params.objectivePos, objectiveSigma), tc.domain, params.discretizationStep, params.protectedRange, params.sensorPerformanceMinimum, params.objectivePos, objectiveSigma); + if length(params.objectiveVar) > 4 && length(params.objectivePos) > 2 + objectiveSigma = permute(reshape(params.objectiveVar, [length(params.objectiveVar)/4 2 2]), [3 1 2]); + objectivePos = reshape(params.objectivePos, [length(params.objectivePos)/2, 2])'; + else + objectiveSigma = reshape(params.objectiveVar, [1, 2, 2]); + objectivePos = params.objectivePos; + end + tc.domain.objective = tc.domain.objective.initialize(objectiveFunctionWrapper(objectivePos, objectiveSigma), tc.domain, params.discretizationStep, params.protectedRange, params.sensorPerformanceMinimum, objectivePos, objectiveSigma); agents = cell(size(params.initialPositions, 2) / 3, 1); for ii = 1:size(agents, 1) From 0b63cb4874b3bb4e606550a9f6b3921dafbaa936 Mon Sep 17 00:00:00 2001 From: Kevin D Date: Mon, 25 May 2026 14:15:05 -0700 Subject: [PATCH 31/32] write initial video frame --- @miSim/run.m | 1 + 1 file changed, 1 insertion(+) diff --git a/@miSim/run.m b/@miSim/run.m index b0517d7..aa0ed7e 100644 --- a/@miSim/run.m +++ b/@miSim/run.m @@ -15,6 +15,7 @@ function [obj] = run(obj) % Capture reference frame size; used to resize frames that deviate % due to figure reflow during plot updates (e.g. in headless mode). I_ref = getframe(obj.f); + v.writeVideo(I_ref); videoFrameSize = [size(I_ref.cdata, 2), size(I_ref.cdata, 1)]; end end From c73559cd69d87dc342767243af03f1e3b5a38a0d Mon Sep 17 00:00:00 2001 From: Kevin D Date: Mon, 25 May 2026 14:51:39 -0700 Subject: [PATCH 32/32] added 2 objective support for AERPAW --- @miSim/initializeFromCsv.m | 15 +++++++- aerpaw/controller.m | 2 +- aerpaw/guidance_step.m | 33 ++++++++++------- aerpaw/impl/controller_impl.cpp | 65 +++++++++++++++++++++++---------- aerpaw/impl/controller_impl.h | 15 ++++---- 5 files changed, 87 insertions(+), 43 deletions(-) diff --git a/@miSim/initializeFromCsv.m b/@miSim/initializeFromCsv.m index e787bf0..f5c5857 100644 --- a/@miSim/initializeFromCsv.m +++ b/@miSim/initializeFromCsv.m @@ -52,8 +52,19 @@ BETA_TILT_VEC = scenario.betaTilt; % 1×N DOMAIN_MIN = scenario.domainMin; % 1×3 DOMAIN_MAX = scenario.domainMax; % 1×3 -OBJECTIVE_GROUND_POS = scenario.objectivePos; % 1×2 -OBJECTIVE_VAR = reshape(scenario.objectiveVar, 2, 2); % 2×2 covariance matrix + +% objectivePos: 2 values per Gaussian component (1 or 2 components supported) +nObjComponents = numel(scenario.objectivePos) / 2; +assert(mod(numel(scenario.objectivePos), 2) == 0, ... + 'objectivePos must have an even number of values (2 per Gaussian component)'); +assert(nObjComponents >= 1 && nObjComponents <= 2, ... + 'At most 2 objective Gaussian components supported; got %d', nObjComponents); +assert(numel(scenario.objectiveVar) == nObjComponents * 4, ... + 'objectiveVar must have %d values for %d component(s); got %d', ... + nObjComponents * 4, nObjComponents, numel(scenario.objectiveVar)); +OBJECTIVE_GROUND_POS = reshape(scenario.objectivePos, 2, nObjComponents)'; % nObj×2 +OBJECTIVE_VAR = permute(reshape(scenario.objectiveVar, 2, 2, nObjComponents), [3, 1, 2]); % nObj×2×2 + SENSOR_PERFORMANCE_MINIMUM = scenario.sensorPerformanceMinimum; % scalar % Initial UAV positions: flat vector reshaped to N×3 diff --git a/aerpaw/controller.m b/aerpaw/controller.m index 9d83aa5..a62197f 100644 --- a/aerpaw/controller.m +++ b/aerpaw/controller.m @@ -58,7 +58,7 @@ if ~coder.target('MATLAB') end % Load guidance scenario from CSV (parameters for guidance_step) -NUM_SCENARIO_PARAMS = 48; +NUM_SCENARIO_PARAMS = 55; MAX_OBSTACLES_CTRL = int32(8); scenarioParams = zeros(1, NUM_SCENARIO_PARAMS); obstacleMin = zeros(MAX_OBSTACLES_CTRL, 3); diff --git a/aerpaw/guidance_step.m b/aerpaw/guidance_step.m index 757d0d5..f674c1c 100644 --- a/aerpaw/guidance_step.m +++ b/aerpaw/guidance_step.m @@ -94,29 +94,34 @@ if isInit BETA_TILT_VEC = scenarioParams(29:32); DOMAIN_MIN = scenarioParams(33:35); DOMAIN_MAX = scenarioParams(36:38); - OBJECTIVE_GROUND_POS = scenarioParams(39:40); - OBJECTIVE_VAR = reshape(scenarioParams(41:44), 2, 2); - SENSOR_PERFORMANCE_MINIMUM = scenarioParams(45); - USE_DOUBLE_INTEGRATOR = logical(scenarioParams(46)); - DAMPING_COEFF = scenarioParams(47); - USE_FIXED_TOPOLOGY = logical(scenarioParams(48)); + NUM_OBJ_COMPONENTS = int32(scenarioParams(39)); + OBJECTIVE_POS_FLAT = scenarioParams(40:43); % [x1,y1,x2,y2]; zero-padded if N=1 + OBJECTIVE_VAR_FLAT = scenarioParams(44:51); % [v11,v12,v21,v22 per component] + SENSOR_PERFORMANCE_MINIMUM = scenarioParams(52); + USE_DOUBLE_INTEGRATOR = logical(scenarioParams(53)); + DAMPING_COEFF = scenarioParams(54); + USE_FIXED_TOPOLOGY = logical(scenarioParams(55)); % --- Build domain geometry --- dom = rectangularPrism; dom = dom.initialize([DOMAIN_MIN; DOMAIN_MAX], REGION_TYPE.DOMAIN, "Guidance Domain"); - % --- Build sensing objective (inline Gaussian; codegen-compatible) --- + % --- Build sensing objective: sum of N bivariate Gaussians (codegen-compatible) --- dom.objective = sensingObjective; xGrid = unique([DOMAIN_MIN(1):DISCRETIZATION_STEP:DOMAIN_MAX(1), DOMAIN_MAX(1)]); yGrid = unique([DOMAIN_MIN(2):DISCRETIZATION_STEP:DOMAIN_MAX(2), DOMAIN_MAX(2)]); [gridX, gridY] = meshgrid(xGrid, yGrid); - dx = gridX - OBJECTIVE_GROUND_POS(1); - dy = gridY - OBJECTIVE_GROUND_POS(2); - % Bivariate Gaussian using objectiveVar covariance matrix (avoids inv()) - ov_a = OBJECTIVE_VAR(1,1); ov_b = OBJECTIVE_VAR(1,2); - ov_c = OBJECTIVE_VAR(2,1); ov_d = OBJECTIVE_VAR(2,2); - ov_det = ov_a * ov_d - ov_b * ov_c; - objValues = exp((-0.5 / ov_det) .* (ov_d .* dx.*dx - (ov_b + ov_c) .* dx.*dy + ov_a .* dy.*dy)); + objValues = zeros(size(gridX)); + for kk = 1:NUM_OBJ_COMPONENTS + pos_k = OBJECTIVE_POS_FLAT((kk-1)*2+1 : (kk-1)*2+2); + var_k = reshape(OBJECTIVE_VAR_FLAT((kk-1)*4+1 : (kk-1)*4+4), 2, 2); + dx = gridX - pos_k(1); + dy = gridY - pos_k(2); + ov_a = var_k(1,1); ov_b = var_k(1,2); + ov_c = var_k(2,1); ov_d = var_k(2,2); + ov_det = ov_a * ov_d - ov_b * ov_c; + objValues = objValues + exp((-0.5 / ov_det) .* (ov_d .* dx.*dx - (ov_b + ov_c) .* dx.*dy + ov_a .* dy.*dy)); + end dom.objective = dom.objective.initializeWithValues(objValues, dom, ... DISCRETIZATION_STEP, PROTECTED_RANGE, SENSOR_PERFORMANCE_MINIMUM); diff --git a/aerpaw/impl/controller_impl.cpp b/aerpaw/impl/controller_impl.cpp index ded16d9..effe0fe 100644 --- a/aerpaw/impl/controller_impl.cpp +++ b/aerpaw/impl/controller_impl.cpp @@ -222,12 +222,13 @@ static int readScenarioDataRow(const char* filename, char* line, int lineSize) { // 28-31: betaTilt[1:4] // 32-34: domainMin (east, north, up) // 35-37: domainMax (east, north, up) -// 38-39: objectivePos (east, north) -// 40-43: objectiveVar (2x2 col-major: v11, v12, v21, v22) -// 44 : sensorPerformanceMinimum (CSV column 18) -// 45 : useDoubleIntegrator (CSV column 23; 0=single-integrator, 1=double-integrator) -// 46 : dampingCoeff (CSV column 24) -// 47 : useFixedTopology (CSV column 25; 0=dynamic lesser-neighbor, 1=fixed) +// 38 : numObjectiveComponents (1 or 2; inferred from objectivePos field length) +// 39-42: objectivePos flat [x1,y1,x2,y2] (4 slots; zero-padded if N=1) +// 43-50: objectiveVar flat [v11,v12,v21,v22 per component] (8 slots; zero-padded if N=1) +// 51 : sensorPerformanceMinimum (CSV column 18) +// 52 : useDoubleIntegrator (CSV column 23) +// 53 : dampingCoeff (CSV column 24) +// 54 : useFixedTopology (CSV column 25) // Returns 1 on success, 0 on failure. int loadScenario(const char* filename, double* params) { char line[4096]; @@ -305,52 +306,78 @@ int loadScenario(const char* filename, double* params) { } } - // objectivePos: column 16 + // objectivePos: column 16 — 2 values per component (up to 2 components). + // Infer numObjectiveComponents from the number of values parsed. { char tmp[256]; strncpy(tmp, fields[16], sizeof(tmp) - 1); tmp[sizeof(tmp)-1] = '\0'; char* t = trimField(tmp); - if (sscanf(t, "%lf , %lf", ¶ms[38], ¶ms[39]) != 2) { - fprintf(stderr, "loadScenario: failed to parse objectivePos: %s\n", t); + double posVals[4] = {0, 0, 0, 0}; + int posCount = 0; + char* tok = strtok(t, ","); + while (tok && posCount < 4) { + posVals[posCount++] = atof(tok); + tok = strtok(nullptr, ","); + } + // Check for a 5th token — would mean > 2 components + if (tok) { + fprintf(stderr, "loadScenario: at most 2 objective Gaussian components supported (objectivePos has >4 values)\n"); return 0; } + if (posCount == 0 || posCount % 2 != 0) { + fprintf(stderr, "loadScenario: objectivePos must have 2 or 4 values, got %d\n", posCount); + return 0; + } + int nObj = posCount / 2; + params[38] = (double)nObj; + for (int k = 0; k < 4; k++) params[39 + k] = posVals[k]; // zero-padded for nObj=1 } - // objectiveVar: column 17, format "v11, v12, v21, v22" + // objectiveVar: column 17 — 4 values per component (v11,v12,v21,v22). { - char tmp[256]; strncpy(tmp, fields[17], sizeof(tmp) - 1); tmp[sizeof(tmp)-1] = '\0'; + char tmp[512]; strncpy(tmp, fields[17], sizeof(tmp) - 1); tmp[sizeof(tmp)-1] = '\0'; char* t = trimField(tmp); - if (sscanf(t, "%lf , %lf , %lf , %lf", ¶ms[40], ¶ms[41], ¶ms[42], ¶ms[43]) != 4) { - fprintf(stderr, "loadScenario: failed to parse objectiveVar: %s\n", t); + int nObj = (int)params[38]; + double varVals[8] = {0, 0, 0, 0, 0, 0, 0, 0}; + int varCount = 0; + char* tok = strtok(t, ","); + while (tok && varCount < 8) { + varVals[varCount++] = atof(tok); + tok = strtok(nullptr, ","); + } + if (varCount != nObj * 4) { + fprintf(stderr, "loadScenario: objectiveVar has %d values but expected %d (4 per component)\n", + varCount, nObj * 4); return 0; } + for (int k = 0; k < 8; k++) params[43 + k] = varVals[k]; // zero-padded for nObj=1 } // sensorPerformanceMinimum: column 18 { char tmp[64]; strncpy(tmp, fields[18], sizeof(tmp) - 1); tmp[sizeof(tmp)-1] = '\0'; - params[44] = atof(trimField(tmp)); + params[51] = atof(trimField(tmp)); } // useDoubleIntegrator: column 23 { char tmp[64]; strncpy(tmp, fields[23], sizeof(tmp) - 1); tmp[sizeof(tmp)-1] = '\0'; - params[45] = atof(trimField(tmp)); + params[52] = atof(trimField(tmp)); } // dampingCoeff: column 24 { char tmp[64]; strncpy(tmp, fields[24], sizeof(tmp) - 1); tmp[sizeof(tmp)-1] = '\0'; - params[46] = atof(trimField(tmp)); + params[53] = atof(trimField(tmp)); } // useFixedTopology: column 25 { char tmp[64]; strncpy(tmp, fields[25], sizeof(tmp) - 1); tmp[sizeof(tmp)-1] = '\0'; - params[47] = atof(trimField(tmp)); + params[54] = atof(trimField(tmp)); } - printf("Loaded scenario: domain [%g,%g,%g] to [%g,%g,%g]\n", - params[32], params[33], params[34], params[35], params[36], params[37]); + printf("Loaded scenario: domain [%g,%g,%g] to [%g,%g,%g], %d objective component(s)\n", + params[32], params[33], params[34], params[35], params[36], params[37], (int)params[38]); return 1; } diff --git a/aerpaw/impl/controller_impl.h b/aerpaw/impl/controller_impl.h index f751f80..28b81c3 100644 --- a/aerpaw/impl/controller_impl.h +++ b/aerpaw/impl/controller_impl.h @@ -27,13 +27,14 @@ int loadTargets(const char* filename, double* targets, int maxClients); // 28-31 betaTilt[1:4] // 32-34 domainMin // 35-37 domainMax -// 38-39 objectivePos -// 40-43 objectiveVar (2x2 col-major) -// 44 sensorPerformanceMinimum -// 45 useDoubleIntegrator (0=single-integrator, 1=double-integrator) -// 46 dampingCoeff -// 47 useFixedTopology (0=dynamic lesser-neighbor, 1=fixed) -#define NUM_SCENARIO_PARAMS 48 +// 38 numObjectiveComponents (1 or 2; inferred from objectivePos field length) +// 39-42 objectivePos flat [x1,y1,x2,y2] (4 slots; zero-padded if N=1) +// 43-50 objectiveVar flat [v11,v12,v21,v22 per component] (8 slots; zero-padded if N=1) +// 51 sensorPerformanceMinimum +// 52 useDoubleIntegrator (0=single-integrator, 1=double-integrator) +// 53 dampingCoeff +// 54 useFixedTopology (0=dynamic lesser-neighbor, 1=fixed) +#define NUM_SCENARIO_PARAMS 55 #define MAX_CLIENTS_PER_PARAM 4 // Maximum number of obstacles (upper bound for pre-allocated arrays). #define MAX_OBSTACLES 8