basin extension replaced

This commit is contained in:
kdee committed 2026-10-11 16:39:46 -07:00
1 parent a55798c599
commit 1e335d33be
5 files changed
+66 -111

No files matched your search

+58 -103
View File
@@ -1,11 +1,11 @@
function obj = lesserSinkNeighbor(obj) function obj = lesserSinkNeighbor(obj)
% LESSERSINKNEIGHBOR Capacity-Aware Lesser Sink Neighbor algorithm (CALSN). % LESSERSINKNEIGHBOR Capacity-Aware Lesser Sink Neighbor algorithm (CALSN).
% %
% Implements the algorithm specified in new/new_routing_algorithm.pdf as the % Implements the algorithm given in the CALSN algorithm description document
% selectable alternative to lesserNeighbor. UAVs 1 and N are base stations; % (ADD; kept outside the repository) as the selectable alternative to
% every other UAV generates r = 1 unit of data per step, and all data must % lesserNeighbor. UAVs 1 and N are base stations; every other UAV generates
% reach a base station. The algorithm outputs both a sparse maintained % r = 1 unit of data per step, and all data must reach a base station. The
% topology and a routing table. % algorithm outputs both a sparse maintained topology and a routing table.
% %
% 1. Base-station potentials phi via distributed Bellman-Ford over the % 1. Base-station potentials phi via distributed Bellman-Ford over the
% current communication neighborhoods (obj.adjacency), with link cost % current communication neighborhoods (obj.adjacency), with link cost
@@ -14,29 +14,31 @@ function obj = lesserSinkNeighbor(obj)
% 2. Lesser sink neighbors L_i replace LNA's "j < i" rule: % 2. Lesser sink neighbors L_i replace LNA's "j < i" rule:
% L_i = { j in N_i : phi_j < phi_i, or phi_j == phi_i and j < i }. % L_i = { j in N_i : phi_j < phi_i, or phi_j == phi_i and j < i }.
% The equal-potential lexicographic tie-break is a documented deviation % The equal-potential lexicographic tie-break is a documented deviation
% from the spec's strict inequality: at an exact potential watershed % from the ADD's strict inequality. It makes (phi, index) a total order,
% (e.g. a symmetric chain) the strict rule would split A_control into % so every feasible link between two non-base UAVs has exactly one
% one tree per base station; the tie-break keeps the airborne graph % deciding endpoint (the uphill one).
% connected, which the mission requires.
% 3. LNA-style sparsity: one parent per connected component of the % 3. LNA-style sparsity: one parent per connected component of the
% L_i-induced subgraph (only links with positive capacity in at least % L_i-induced subgraph (only links with positive capacity in at least
% one direction count as edges), chosen by % one direction count as edges), chosen by
% p = argmin_{j in C} ( phi_j + 1/(C_ij + eps) ). % p = argmin_{j in C} ( phi_j + 1/(C_ij + eps) ).
% A_control(i,p) = A_control(p,i) = 1 (diagonal = I). % A_control(i,p) = A_control(p,i) = 1 (diagonal = I).
% BASIN EXTENSION (documented deviation): with two potential minima % 4. Connectivity bridge (documented deviation; not in the ADD). Every UAV
% (the two bases), one-parent-per-component alone yields a spanning % must stay connected to the whole fleet to exchange positions for
% forest with up to two disjoint base-rooted trees whenever a UAV's % guidance, so A_control must be one connected graph. LNA's grouping
% lesser sink neighbors collapse into a single component (dense usable % argument guarantees that whenever every feasible link has a deciding
% graphs). To restore LNA's global-connectivity property, each UAV % endpoint. The one exception is the base-to-base link: both bases have
% selects one representative per component AND per base-station BASIN % phi = 0 and select no parents, so nobody maintains it, yet it still
% (the base a neighbor's potential descent terminates at). Interior % merges groups when other UAVs form their components. Steps 1-3
% UAVs are unaffected (their whole neighborhood shares one basin); % therefore leave A_control either connected or split into exactly two
% watershed UAVs whose neighborhoods span both basins gain a second % pieces, one containing each base. In the split case one bridge is
% parent, which is exactly the bridge between the two trees. A final % added: of all feasible links between the two pieces (base-to-base
% stitch guard covers degenerate geometries: if A_control still has % included), the one whose weaker direction has the highest SINR, i.e.
% multiple components, the lowest-cost feasible inter-component link % the most margin under the comms barrier. A warning is issued if even
% is added (maintained for connectivity only; carries no flow). % that link is inside the CBF maintenance band (sinrThreshold + 3 dB).
% 4. Routing, children first (decreasing phi; equal phi processed in % The bridge is maintained for connectivity only and carries no flow,
% so when present it is the one maintained link outside the ADD's
% invariant A_control(i,j) = 1, i ~= j, iff F(i,j) > 0 or F(j,i) > 0.
% 5. Routing, children first (decreasing phi; equal phi processed in
% decreasing index order so tie-break children precede their parents): % decreasing index order so tie-break children precede their parents):
% aggregate demand D_i = r + inflows, split capacity-proportionally % aggregate demand D_i = r + inflows, split capacity-proportionally
% over parents; saturated links are capped at capacity and the residual % over parents; saturated links are capped at capacity and the residual
@@ -72,13 +74,16 @@ function obj = lesserSinkNeighbor(obj)
positions(kk, :) = obj.agents{kk}.pos; positions(kk, :) = obj.agents{kk}.pos;
end end
% Directed Shannon link capacities over the current communication % Directed SINRs and Shannon link capacities over the current
% neighborhoods: C(i, j) = capacity of sending from i to j (receiver j) % communication neighborhoods: S(i, j) and C(i, j) describe sending
% from i to j (receiver j)
S = zeros(nAgents);
C = zeros(nAgents); C = zeros(nAgents);
for ii = 1:nAgents for ii = 1:nAgents
for jj = 1:nAgents for jj = 1:nAgents
if jj ~= ii && obj.adjacency(ii, jj) if jj ~= ii && obj.adjacency(ii, jj)
C(ii, jj) = obj.bandwidth * log2(1 + obj.sinrLink(positions, jj, ii)); S(ii, jj) = obj.sinrLink(positions, jj, ii);
C(ii, jj) = obj.bandwidth * log2(1 + S(ii, jj));
end end
end end
end end
@@ -109,42 +114,7 @@ function obj = lesserSinkNeighbor(obj)
end end
end end
% ---- Base-station basins (for the basin extension) ------------------- % ---- Parent selection (one per lesser-sink component) ---------------
% Each finite-potential UAV descends its argmin neighbor chain; phi is
% strictly decreasing along it, so the chain terminates at a base. The
% basin label records which base that is.
descent = zeros(nAgents, 1);
for ii = 1:nAgents
if isBase(ii) || isinf(phi(ii))
continue;
end
bestMetric = inf;
for jj = 1:nAgents
if jj == ii || ~obj.adjacency(ii, jj)
continue;
end
metric = phi(jj) + 1 / (C(ii, jj) + EPS_CAP);
if metric < bestMetric
bestMetric = metric;
descent(ii) = jj;
end
end
end
basin = zeros(nAgents, 1);
basin(1) = 1;
basin(nAgents) = nAgents;
for ii = 1:nAgents
if isBase(ii) || isinf(phi(ii))
continue;
end
kk = ii;
while ~isBase(kk)
kk = descent(kk);
end
basin(ii) = kk;
end
% ---- Parent selection (one per lesser-sink component and basin) ------
Acontrol = logical(eye(nAgents)); Acontrol = logical(eye(nAgents));
isParent = false(nAgents); % isParent(i, p): p is a selected parent of i isParent = false(nAgents); % isParent(i, p): p is a selected parent of i
for ii = 1:nAgents for ii = 1:nAgents
@@ -187,8 +157,7 @@ function obj = lesserSinkNeighbor(obj)
end end
end end
% One parent per connected component and per base-station basin % One parent per connected component, selected by capacity and
% present in it (basin extension), selected by capacity and
% potential: p = argmin( phi_j + 1/(C_ij + eps) ) % potential: p = argmin( phi_j + 1/(C_ij + eps) )
visited = false(1, lsnCount); visited = false(1, lsnCount);
for aa = 1:lsnCount for aa = 1:lsnCount
@@ -198,72 +167,58 @@ function obj = lesserSinkNeighbor(obj)
component = bfs(subgraphAdjacency, aa); component = bfs(subgraphAdjacency, aa);
visited(component) = true; visited(component) = true;
for bb = [1, nAgents] % the two possible basin labels
bestMetric = inf; bestMetric = inf;
bestParent = 0; bestParent = 0;
for cc = 1:numel(component) for cc = 1:numel(component)
jj = lsn(component(cc)); jj = lsn(component(cc));
if basin(jj) ~= bb
continue;
end
metric = phi(jj) + 1 / (C(ii, jj) + EPS_CAP); metric = phi(jj) + 1 / (C(ii, jj) + EPS_CAP);
if metric < bestMetric if metric < bestMetric
bestMetric = metric; bestMetric = metric;
bestParent = jj; bestParent = jj;
end end
end end
if bestParent > 0
isParent(ii, bestParent) = true; isParent(ii, bestParent) = true;
Acontrol(ii, bestParent) = true; Acontrol(ii, bestParent) = true;
Acontrol(bestParent, ii) = true; Acontrol(bestParent, ii) = true;
end end
end end
end
end
% ---- Connectivity stitch guard --------------------------------------- % ---- Connectivity bridge ---------------------------------------------
% The basin extension bridges the two base-rooted trees through % The unmaintained base-to-base link is the only way steps 1-3 can
% watershed UAVs whenever each base borders a routing basin. Degenerate % leave A_control split, and then into exactly two pieces, one per
% geometries (e.g. a base whose only feasible neighbor is the other % base. Bridge them with the feasible cross link whose weaker direction
% base) can still leave A_control split; add the lowest-cost feasible % has the highest SINR. If the pieces are not joined by any feasible
% link between components until one remains. Stitched links are % link, the feasibility graph itself is disconnected; validate()
% maintained for connectivity only and carry no routed flow. If the
% feasibility graph itself is disconnected no stitch exists; validate()
% reports that case. % reports that case.
while true pieceOfBase1 = false(1, nAgents);
comp = zeros(1, nAgents); pieceOfBase1(bfs(Acontrol, 1)) = true;
nComp = 0; if ~pieceOfBase1(nAgents)
for ii = 1:nAgents bestSinr = -inf;
if comp(ii) == 0
nComp = nComp + 1;
comp(bfs(Acontrol, ii)) = nComp;
end
end
if nComp <= 1
break;
end
bestMetric = inf;
bestU = 0; bestU = 0;
bestV = 0; bestV = 0;
for uu = 1:nAgents for uu = find(pieceOfBase1)
for vv = (uu + 1):nAgents for vv = find(~pieceOfBase1)
if comp(uu) ~= comp(vv) && obj.adjacency(uu, vv) if obj.adjacency(uu, vv)
% Bidirectional link cost (finite even for infinite sinrBinding = min(S(uu, vv), S(vv, uu));
% potentials, so isolated islands remain stitchable) if sinrBinding > bestSinr
metric = 1 / (C(uu, vv) + EPS_CAP) + 1 / (C(vv, uu) + EPS_CAP); bestSinr = sinrBinding;
if metric < bestMetric
bestMetric = metric;
bestU = uu; bestU = uu;
bestV = vv; bestV = vv;
end end
end end
end end
end end
if bestU == 0 if bestU > 0
break; % feasibility graph disconnected: nothing to stitch with
end
Acontrol(bestU, bestV) = true; Acontrol(bestU, bestV) = true;
Acontrol(bestV, bestU) = true; Acontrol(bestV, bestU) = true;
% CBF maintenance band: constrainMotion holds maintained links
% at sinrThreshold + 3 dB (paddingDb; keep in sync)
if bestSinr < 10^((obj.sinrThreshold + 3.0) / 10)
warning("miSim:bridgeInCbfBand", ...
"Connectivity bridge %d-%d has SINR %.1f dB, inside the CBF maintenance band (sinrThreshold %.1f dB + 3 dB)", ...
bestU, bestV, 10 * log10(bestSinr), obj.sinrThreshold);
end
end
end end
% ---- Routing (children first: decreasing potential order) ----------- % ---- Routing (children first: decreasing potential order) -----------
+4 -4
View File
@@ -32,10 +32,10 @@ classdef miSim
bandwidth = 20e6; % channel bandwidth (Hz) for thermal noise N = k_B*T*B bandwidth = 20e6; % channel bandwidth (Hz) for thermal noise N = k_B*T*B
% Topology-selection algorithm (requires useSinrComms; MATLAB sim only). % Topology-selection algorithm (requires useSinrComms; MATLAB sim only).
% false -> lesser-neighbor; true -> Capacity-Aware Lesser Sink Neighbor % false -> lesser-neighbor; true -> Capacity-Aware Lesser Sink Neighbor
% (CALSN, see new/new_routing_algorithm.pdf): base-station potentials % (CALSN, see the algorithm description document, ADD): base-station
% replace the index-based "lesser" rule, and the maintained topology % potentials replace the index-based "lesser" rule, and the maintained
% comes with a routing table (flows F, fractions R) that sends every % topology comes with a routing table (flows F, fractions R) that sends
% UAV's data toward base stations 1 and N. % every UAV's data toward base stations 1 and N.
useRoutingTopology = false; useRoutingTopology = false;
routingAdjacencyMatrix = false(0, 0); % directed flow links (i, j) = child i sends to parent j (F > 0) routingAdjacencyMatrix = false(0, 0); % directed flow links (i, j) = child i sends to parent j (F > 0)
routingFlows = zeros(0, 0); % F: flow on each directed link (units of r = 1 per UAV per step) routingFlows = zeros(0, 0); % F: flow on each directed link (units of r = 1 per UAV per step)
+1 -1
View File
@@ -60,7 +60,7 @@ function [sinr, grad] = sinrLink(obj, positions, rxIdx, txIdx)
S = P_t / (K * d_t^n); S = P_t / (K * d_t^n);
% Interference I = sum of received powers from every other agent at r % Interference I = sum of received powers from every other agent at r
I = 0.0; I = 0;
for k = 1:nAgents for k = 1:nAgents
if k == r || k == t if k == r || k == t
continue; continue;
+1 -1
View File
@@ -20,7 +20,7 @@ function validate(obj)
end end
% In routing (CALSN) mode the MAINTAINED topology itself must form a % In routing (CALSN) mode the MAINTAINED topology itself must form a
% single connected component (basin extension + stitch guard invariant) % single connected component (guaranteed by the connectivity bridge)
if obj.useRoutingTopology && max(conncomp(graph(obj.constraintAdjacencyMatrix))) ~= 1 if obj.useRoutingTopology && max(conncomp(graph(obj.constraintAdjacencyMatrix))) ~= 1
error("Maintained routing topology (A_control) is not connected"); error("Maintained routing topology (A_control) is not connected");
end end
+2 -2
View File
@@ -1007,7 +1007,7 @@ classdef test_miSim < matlab.unittest.TestCase
% corner, co-altitude at z = 2. Base 1 trails the fleet and % corner, co-altitude at z = 2. Base 1 trails the fleet and
% base 4 leads it, with the relays staggered between: relay 3 % base 4 leads it, with the relays staggered between: relay 3
% starts too far from base 1 for a direct link to be worth % starts too far from base 1 for a direct link to be worth
% using, so its initial basin-1 route is MULTI-HOP via relay 2 % using, so its initial route to base 1 is MULTI-HOP via relay 2
% and re-parents to the base directly as the fleet contracts. % and re-parents to the base directly as the fleet contracts.
tc.agents = {agent; agent; agent; agent;}; tc.agents = {agent; agent; agent; agent;};
tc.collisionRanges = .25 * ones(size(tc.agents)); tc.collisionRanges = .25 * ones(size(tc.agents));
@@ -1128,7 +1128,7 @@ classdef test_miSim < matlab.unittest.TestCase
% corner, co-altitude at z = 2. Base 1 trails the fleet and % corner, co-altitude at z = 2. Base 1 trails the fleet and
% base 4 leads it, with the relays staggered between: relay 3 % base 4 leads it, with the relays staggered between: relay 3
% starts too far from base 1 for a direct link to be worth % starts too far from base 1 for a direct link to be worth
% using, so its initial basin-1 route is MULTI-HOP via relay 2 % using, so its initial route to base 1 is MULTI-HOP via relay 2
% and re-parents to the base directly as the fleet contracts. % and re-parents to the base directly as the fleet contracts.
tc.agents = {agent; agent; agent; agent;}; tc.agents = {agent; agent; agent; agent;};
tc.collisionRanges = .25 * ones(size(tc.agents)); tc.collisionRanges = .25 * ones(size(tc.agents));