added routing logic and network graph improvements
This commit is contained in:
1 parent
1d414e2bd4
commit
56cc4fced5
3 files changed
+142
-26
No files matched your search
+52
-25
@@ -6,8 +6,7 @@ function obj = plotGraph(obj)
|
||||
obj (1, 1) {mustBeA(obj, "miSim")};
|
||||
end
|
||||
|
||||
% Form graph from adjacency matrix
|
||||
G = graph(obj.constraintAdjacencyMatrix, "omitselfloops");
|
||||
nAgents = size(obj.agents, 1);
|
||||
|
||||
% Resolve the target axes for the network graph tile
|
||||
if isnan(obj.networkGraphIndex)
|
||||
@@ -16,7 +15,36 @@ function obj = plotGraph(obj)
|
||||
ax = obj.f.Children(1).Children(obj.networkGraphIndex(1));
|
||||
end
|
||||
|
||||
% Plot graph object
|
||||
% Build the graph to plot. The fixed-radius model is undirected. In SINR
|
||||
% mode the link set (lesser-neighbor topology) is kept exactly as-is, but
|
||||
% each connection is given an orientation: of the two directional SINRs on
|
||||
% a link, the larger one wins, and the edge is directed from that
|
||||
% direction's transmitter to its receiver (arrow = transmitter -> receiver).
|
||||
if obj.useSinrComms
|
||||
positions = zeros(nAgents, 3);
|
||||
for kk = 1:nAgents
|
||||
positions(kk, :) = obj.agents{kk}.pos;
|
||||
end
|
||||
D = false(nAgents); % directed adjacency: D(tx, rx) = true
|
||||
for aa = 1:(nAgents - 1)
|
||||
for bb = (aa + 1):nAgents
|
||||
if obj.constraintAdjacencyMatrix(aa, bb)
|
||||
sinrARx = obj.sinrLink(positions, aa, bb); % aa receives bb
|
||||
sinrBRx = obj.sinrLink(positions, bb, aa); % bb receives aa
|
||||
if sinrARx >= sinrBRx
|
||||
D(bb, aa) = true; % better as bb -> aa (tx bb, rx aa)
|
||||
else
|
||||
D(aa, bb) = true; % better as aa -> bb (tx aa, rx bb)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
G = digraph(D);
|
||||
else
|
||||
G = graph(obj.constraintAdjacencyMatrix, "omitselfloops");
|
||||
end
|
||||
|
||||
% Plot graph object (digraph renders arrowheads automatically)
|
||||
hold(ax, "on");
|
||||
o = plot(ax, G, "LineStyle", "--", "EdgeColor", "g", "NodeColor", "k", "LineWidth", 2);
|
||||
hold(ax, "off");
|
||||
@@ -26,34 +54,33 @@ function obj = plotGraph(obj)
|
||||
end
|
||||
end
|
||||
|
||||
% In SINR comms mode, label each edge with the connection's SINR and report
|
||||
% the connectivity threshold in the tile title. The fixed-radius model is
|
||||
% left unchanged (no edge labels, title stays as set in firstPlotSetup).
|
||||
if obj.useSinrComms
|
||||
endNodes = G.Edges.EndNodes; % each row [lo hi]; graph stores lo < hi
|
||||
nAgents = size(obj.agents, 1);
|
||||
positions = zeros(nAgents, 3);
|
||||
for kk = 1:nAgents
|
||||
positions(kk, :) = obj.agents{kk}.pos;
|
||||
% Mark ground-station-connected nodes with an "x" (others keep the default
|
||||
% dot). Ground stations attach to the two "router" agents: agent 1 and the
|
||||
% agent with the largest ODD index (that is nAgents when odd, else
|
||||
% nAgents-1). With one or two agents only agent 1 qualifies.
|
||||
routers = unique([1, 2 * ceil(nAgents / 2) - 1]);
|
||||
for oo = 1:numel(o)
|
||||
highlight(o(oo), routers, "Marker", "x", "MarkerSize", 8);
|
||||
end
|
||||
% A link is feasible only if BOTH directions clear the threshold, so
|
||||
% label each edge with the binding (worse) directional SINR: the min of
|
||||
% the lower-index-receiver and higher-index-receiver links, with
|
||||
% interference from all other agents at each receiver. Labels are bare
|
||||
% numbers (no units) so they stay compact; GraphPlot renders them
|
||||
|
||||
% In SINR comms mode, label each directed edge with the better SINR that set
|
||||
% its orientation and report the threshold in the tile title. The
|
||||
% fixed-radius model is left unchanged (no edge labels, title from setup).
|
||||
if obj.useSinrComms
|
||||
endNodes = G.Edges.EndNodes; % each row [tx rx] for a directed edge
|
||||
% Label with the SINR of the chosen (better) direction: the receiver is
|
||||
% the edge target, the transmitter the edge source. Bare numbers (no
|
||||
% units) keep the small tile uncluttered; GraphPlot draws labels
|
||||
% horizontally, left-to-right.
|
||||
edgeLabels = strings(size(endNodes, 1), 1);
|
||||
for ee = 1:size(endNodes, 1)
|
||||
lo = endNodes(ee, 1);
|
||||
hi = endNodes(ee, 2);
|
||||
sinrLoRx = obj.sinrLink(positions, lo, hi); % receiver lo (lower index)
|
||||
sinrHiRx = obj.sinrLink(positions, hi, lo); % receiver hi (higher index)
|
||||
edgeLabels(ee) = sprintf("%.1f", 10 * log10(min(sinrLoRx, sinrHiRx)));
|
||||
sinrLin = obj.sinrLink(positions, endNodes(ee, 2), endNodes(ee, 1)); % rx, tx
|
||||
edgeLabels(ee) = sprintf("%.1f", 10 * log10(sinrLin));
|
||||
end
|
||||
o(1).EdgeLabel = edgeLabels;
|
||||
% Threshold goes in the existing single-line title (not a subtitle) so
|
||||
% the small tile is not compressed further, and the hover toolbar is
|
||||
% disabled so the R2026 "..." menu stops covering the readout.
|
||||
% Threshold in the existing single-line title (not a subtitle) so the
|
||||
% small tile isn't compressed, and disable the hover toolbar so the
|
||||
% R2026 "..." menu stops covering the readout.
|
||||
title(ax, sprintf("Network Graph (\\geq %.1f dB)", obj.sinrThreshold));
|
||||
ax.Toolbar.Visible = "off";
|
||||
end
|
||||
|
||||
@@ -36,7 +36,14 @@ function f = plot(obj, ind, f)
|
||||
% Add to other perspectives
|
||||
if size(ind, 2) > 1
|
||||
for ii = 2:size(ind, 2)
|
||||
copyobj(o, f.Children(1).Children(ind(ii)));
|
||||
axCopy = f.Children(1).Children(ind(ii));
|
||||
copyobj(o, axCopy);
|
||||
% copyobj does not carry color scaling. The objective values are
|
||||
% sum-normalized (tiny), so without matching CLim the copied surface
|
||||
% is squished to one color once the destination axes' auto CLim is
|
||||
% widened by later surfaces (e.g. agent geometries). Pin it to the
|
||||
% source range so the heatmap renders as a visible gradient.
|
||||
clim(axCopy, cRange);
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -877,6 +877,88 @@ classdef test_miSim < matlab.unittest.TestCase
|
||||
% Run the simulation
|
||||
tc.testClass = tc.testClass.run();
|
||||
end
|
||||
function test_communications_constraint_SINR_threshold_3_agents(tc)
|
||||
% No obstacles
|
||||
% Fixed three agents initial conditions
|
||||
% Negligible collision geometries
|
||||
% Non-standard domain with two objectives that will try to pull the
|
||||
% agents apart
|
||||
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([2, 8; 8, 8]), tc.domain, tc.discretizationStep, tc.protectedRange);
|
||||
|
||||
% Initialize agent collision geometry
|
||||
tc.agents = {agent; agent; agent;};
|
||||
tc.collisionRanges = .25 * ones(size(tc.agents));
|
||||
d = [2.0, 0, 0]; % 2 m collinear spacing (above the 1 m path-loss floor)
|
||||
geometry1 = spherical;
|
||||
geometry2 = geometry1;
|
||||
geometry3 = geometry1;
|
||||
geometry1 = geometry1.initialize(tc.domain.center - d, tc.collisionRanges(1), REGION_TYPE.COLLISION);
|
||||
geometry2 = geometry2.initialize(tc.domain.center, tc.collisionRanges(2), REGION_TYPE.COLLISION);
|
||||
geometry3 = geometry3.initialize(tc.domain.center + d, tc.collisionRanges(3), REGION_TYPE.COLLISION);
|
||||
|
||||
% Initialize agent sensor model
|
||||
tc.sensor = sigmoidSensor;
|
||||
tc.sensor = tc.sensor.initialize(tc.minDimension / 2, 3, 15, 3);
|
||||
|
||||
% Initialize obstacles
|
||||
tc.obstacles = {};
|
||||
|
||||
% Initialize agents
|
||||
tc.maxIter = 50;
|
||||
tc.commsRanges = 4 * ones(size(tc.agents)); % unused in SINR mode; kept for parity with the fixed-radius variant
|
||||
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, geometry2, tc.sensor, tc.commsRanges(2), tc.maxIter, tc.initialStepSize, tc.initialMaxAngleStepSize);
|
||||
tc.agents{3} = tc.agents{3}.initialize(tc.domain.center + d, geometry3, tc.sensor, tc.commsRanges(3), tc.maxIter, tc.initialStepSize, tc.initialMaxAngleStepSize);
|
||||
|
||||
% SINR communications model parameters. Three collinear, equally
|
||||
% spaced agents with homogeneous transmit power form an
|
||||
% INTERFERENCE-limited network (co-agent interference swamps thermal
|
||||
% noise), unlike the two-agent noise-limited case. Two structural
|
||||
% facts set the connectivity boundaries:
|
||||
% * A chain link (1-2 or 2-3) is limited by the MIDDLE agent, which
|
||||
% receives one end while the other end interferes at the SAME
|
||||
% range: signal = interference -> 0 dB.
|
||||
% * The end-to-end (1,3) link has its transmitter at 2x the range
|
||||
% while the middle agent interferes at 1x: (1/2)^n -> -6.02 dB.
|
||||
% Homogeneous power is required for the symmetry: unequal power would
|
||||
% push one chain link's middle-receiver SINR below 0 dB and break it.
|
||||
% Placing the threshold between the two boundaries (a couple dB above
|
||||
% the -6 dB direct link, a few dB below the 0 dB chain links) makes
|
||||
% exactly the two adjacent links {1-2, 2-3} feasible and rejects 1-3,
|
||||
% giving the desired 1 -> 2 -> 3 line. The +3 dB CBF padding then
|
||||
% still lands below the 0 dB chain binding, so the QP starts feasible.
|
||||
useSinrComms = true;
|
||||
txPower = [0.1, 0.1, 0.1]; % homogeneous transmit power (W)
|
||||
pathLossExponent = 2.0;
|
||||
ambientTemp = 290; % K
|
||||
centerFreq = 2.4e9; % Hz
|
||||
bandwidth = 20e6; % Hz
|
||||
|
||||
endBindingDb = 10 * log10(0.5^pathLossExponent); % (1,3) link SINR: (1/2)^n = -6.02 dB
|
||||
sinrThreshold = endBindingDb + 2.0; % ~ -4.02 dB: 2 dB above the rejected
|
||||
% 1-3 link, ~4 dB below the 0 dB chain
|
||||
|
||||
tc.agents{1}.txPower = txPower(1);
|
||||
tc.agents{2}.txPower = txPower(2);
|
||||
tc.agents{3}.txPower = txPower(3);
|
||||
|
||||
% Initialize the simulation in SINR comms mode
|
||||
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, useSinrComms, sinrThreshold, pathLossExponent, ambientTemp, centerFreq, bandwidth);
|
||||
|
||||
% The initial SINR topology must be exactly the 1 -> 2 -> 3 line:
|
||||
% two links (1-2 and 2-3), with the direct 1-3 link rejected.
|
||||
tc.assertEqual(tc.testClass.constraintAdjacencyMatrix, logical( ...
|
||||
[ 1, 1, 0; ...
|
||||
1, 1, 1; ...
|
||||
0, 1, 1;]));
|
||||
|
||||
% Run the simulation
|
||||
tc.testClass = tc.testClass.run();
|
||||
end
|
||||
function test_communications_constraint_fixed_radius(tc)
|
||||
% No obstacles
|
||||
% Fixed two agents initial conditions
|
||||
|
||||
Reference in new issue
Block a user