new routing algorithm

This commit is contained in:
kdee committed 2026-07-22 17:37:04 -07:00
1 parent 37598479cc
commit acfb2719e2
11 files changed
+671 -351

No files matched your search

+372
View File
@@ -0,0 +1,372 @@
function obj = lesserSinkNeighbor(obj)
% LESSERSINKNEIGHBOR Capacity-Aware Lesser Sink Neighbor algorithm (CALSN).
%
% Implements the algorithm specified in new/new_routing_algorithm.pdf as the
% selectable alternative to lesserNeighbor. UAVs 1 and N are base stations;
% every other UAV generates r = 1 unit of data per step, and all data must
% reach a base station. The algorithm outputs both a sparse maintained
% topology and a routing table.
%
% 1. Base-station potentials phi via distributed Bellman-Ford over the
% current communication neighborhoods (obj.adjacency), with link cost
% c_ij = 1/(C_ij + eps), where C_ij = bandwidth * log2(1 + SINR_ij) is
% the Shannon capacity of direction i -> j.
% 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 }.
% The equal-potential lexicographic tie-break is a documented deviation
% from the spec's strict inequality: at an exact potential watershed
% (e.g. a symmetric chain) the strict rule would split A_control into
% one tree per base station; the tie-break keeps the airborne graph
% connected, which the mission requires.
% 3. LNA-style sparsity: one parent per connected component of the
% L_i-induced subgraph (only links with positive capacity in at least
% one direction count as edges), chosen by
% p = argmin_{j in C} ( phi_j + 1/(C_ij + eps) ).
% A_control(i,p) = A_control(p,i) = 1 (diagonal = I).
% BASIN EXTENSION (documented deviation): with two potential minima
% (the two bases), one-parent-per-component alone yields a spanning
% forest with up to two disjoint base-rooted trees whenever a UAV's
% lesser sink neighbors collapse into a single component (dense usable
% graphs). To restore LNA's global-connectivity property, each UAV
% selects one representative per component AND per base-station BASIN
% (the base a neighbor's potential descent terminates at). Interior
% UAVs are unaffected (their whole neighborhood shares one basin);
% watershed UAVs whose neighborhoods span both basins gain a second
% parent, which is exactly the bridge between the two trees. A final
% stitch guard covers degenerate geometries: if A_control still has
% multiple components, the lowest-cost feasible inter-component link
% is added (maintained for connectivity only; carries no flow).
% 4. Routing, children first (decreasing phi; equal phi processed in
% decreasing index order so tie-break children precede their parents):
% aggregate demand D_i = r + inflows, split capacity-proportionally
% over parents; saturated links are capped at capacity and the residual
% redistributed over unsaturated parents; any demand beyond the total
% parent capacity is reported as unrouted and the UAV marked infeasible.
%
% Outputs (set on obj):
% constraintAdjacencyMatrix A_control: undirected maintained topology
% routingFlows F: flow on each directed link (units of r)
% routingFractions R = F/D: share of the sender's demand, 0..1
% routingAdjacencyMatrix F > 0 (directed, child -> parent)
% routingPotentials phi (base-station potential per UAV)
% routingUnrouted unrouted demand per UAV (zeros when feasible)
arguments (Input)
obj (1, 1) {mustBeA(obj, "miSim")};
end
arguments (Output)
obj (1, 1) {mustBeA(obj, "miSim")};
end
nAgents = size(obj.agents, 1);
r = 1.0; % data generated per non-base UAV per step (abstract unit)
EPS_CAP = 1e-12; % epsilon in c = 1/(C + eps); avoids division by zero
TOL = 1e-12; % residual-demand tolerance for the saturation loop
isBase = false(nAgents, 1);
isBase(1) = true;
isBase(nAgents) = true; % base stations are UAVs 1 and N
positions = zeros(nAgents, 3);
for kk = 1:nAgents
positions(kk, :) = obj.agents{kk}.pos;
end
% Directed Shannon link capacities over the current communication
% neighborhoods: C(i, j) = capacity of sending from i to j (receiver j)
C = zeros(nAgents);
for ii = 1:nAgents
for jj = 1:nAgents
if jj ~= ii && obj.adjacency(ii, jj)
C(ii, jj) = obj.bandwidth * log2(1 + obj.sinrLink(positions, jj, ii));
end
end
end
% ---- Base-station potentials (Bellman-Ford, at most N-1 sweeps) -----
phi = inf(nAgents, 1);
phi(isBase) = 0;
for sweep = 1:max(nAgents - 1, 1)
changed = false;
for ii = 1:nAgents
if isBase(ii)
continue;
end
newPhi = inf;
for jj = 1:nAgents
if jj == ii || ~obj.adjacency(ii, jj)
continue;
end
newPhi = min(newPhi, phi(jj) + 1 / (C(ii, jj) + EPS_CAP));
end
if newPhi ~= phi(ii)
phi(ii) = newPhi;
changed = true;
end
end
if ~changed
break;
end
end
% ---- Base-station basins (for the basin extension) -------------------
% 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));
isParent = false(nAgents); % isParent(i, p): p is a selected parent of i
for ii = 1:nAgents
if isBase(ii) || isinf(phi(ii))
% Base stations select no parents; an infinite potential means
% no current local route toward a base station
continue;
end
% Lesser sink neighbors (with the equal-potential tie-break)
lsnBuf = zeros(1, nAgents);
lsnCount = 0;
for jj = 1:nAgents
if jj == ii || ~obj.adjacency(ii, jj)
continue;
end
if phi(jj) < phi(ii) || (phi(jj) == phi(ii) && jj < ii)
lsnCount = lsnCount + 1;
lsnBuf(lsnCount) = jj;
end
end
if lsnCount == 0
continue;
end
lsn = lsnBuf(1:lsnCount);
% Subgraph induced by the lesser sink neighbors in the usable
% communication graph: only links with positive capacity in at
% least one direction count as edges (a zero-capacity link cannot
% carry data, so it must not merge two components)
subgraphAdjacency = false(lsnCount);
for aa = 1:lsnCount
for bb = (aa + 1):lsnCount
ja = lsn(aa);
jb = lsn(bb);
if obj.adjacency(ja, jb) && (C(ja, jb) > 0 || C(jb, ja) > 0)
subgraphAdjacency(aa, bb) = true;
subgraphAdjacency(bb, aa) = true;
end
end
end
% One parent per connected component and per base-station basin
% present in it (basin extension), selected by capacity and
% potential: p = argmin( phi_j + 1/(C_ij + eps) )
visited = false(1, lsnCount);
for aa = 1:lsnCount
if visited(aa)
continue;
end
component = bfs(subgraphAdjacency, aa);
visited(component) = true;
for bb = [1, nAgents] % the two possible basin labels
bestMetric = inf;
bestParent = 0;
for cc = 1:numel(component)
jj = lsn(component(cc));
if basin(jj) ~= bb
continue;
end
metric = phi(jj) + 1 / (C(ii, jj) + EPS_CAP);
if metric < bestMetric
bestMetric = metric;
bestParent = jj;
end
end
if bestParent > 0
isParent(ii, bestParent) = true;
Acontrol(ii, bestParent) = true;
Acontrol(bestParent, ii) = true;
end
end
end
end
% ---- Connectivity stitch guard ---------------------------------------
% The basin extension bridges the two base-rooted trees through
% watershed UAVs whenever each base borders a routing basin. Degenerate
% geometries (e.g. a base whose only feasible neighbor is the other
% base) can still leave A_control split; add the lowest-cost feasible
% link between components until one remains. Stitched links are
% maintained for connectivity only and carry no routed flow. If the
% feasibility graph itself is disconnected no stitch exists; validate()
% reports that case.
while true
comp = zeros(1, nAgents);
nComp = 0;
for ii = 1:nAgents
if comp(ii) == 0
nComp = nComp + 1;
comp(bfs(Acontrol, ii)) = nComp;
end
end
if nComp <= 1
break;
end
bestMetric = inf;
bestU = 0;
bestV = 0;
for uu = 1:nAgents
for vv = (uu + 1):nAgents
if comp(uu) ~= comp(vv) && obj.adjacency(uu, vv)
% Bidirectional link cost (finite even for infinite
% potentials, so isolated islands remain stitchable)
metric = 1 / (C(uu, vv) + EPS_CAP) + 1 / (C(vv, uu) + EPS_CAP);
if metric < bestMetric
bestMetric = metric;
bestU = uu;
bestV = vv;
end
end
end
end
if bestU == 0
break; % feasibility graph disconnected: nothing to stitch with
end
Acontrol(bestU, bestV) = true;
Acontrol(bestV, bestU) = true;
end
% ---- Routing (children first: decreasing potential order) -----------
% Equal potentials are processed in decreasing index order: with the
% lexicographic tie-break, flow within an equal-potential group goes
% from higher to lower index, so higher indices must aggregate first.
F = zeros(nAgents);
R = zeros(nAgents);
unrouted = zeros(nAgents, 1);
nonBase = find(~isBase);
[~, ord] = sortrows([phi(nonBase), nonBase], [-1, -2]);
order = nonBase(ord);
for oo = 1:numel(order)
ii = order(oo);
% Aggregated outgoing demand: own data plus everything received
D = r + sum(F(:, ii));
parents = find(isParent(ii, :));
if D == 0 || isempty(parents)
continue;
end
% Capacity-proportional split, saturating full links first and
% redistributing the remainder across parents with remaining
% capacity. Each pass either routes all residual demand or
% saturates at least one parent, so the loop terminates.
capP = C(ii, parents);
flows = zeros(1, numel(parents));
active = true(1, numel(parents));
residual = D;
while residual > TOL && any(active)
totalCap = sum(capP(active));
if totalCap <= 0
break;
end
alloc = zeros(1, numel(parents));
alloc(active) = residual .* capP(active) ./ totalCap;
flows = flows + alloc;
saturated = active & (flows > capP);
if any(saturated)
residual = sum(flows(saturated) - capP(saturated));
flows(saturated) = capP(saturated);
active(saturated) = false;
else
residual = 0;
end
end
if residual > TOL
% Demand exceeds the total parent capacity: route as much as
% possible and report the remainder as unrouted (infeasible)
unrouted(ii) = residual;
warning("miSim:routingInfeasible", ...
"UAV %d demand %.3g exceeds total parent capacity; %.3g units unrouted", ...
ii, D, residual);
end
F(ii, parents) = flows;
R(ii, parents) = flows ./ D;
end
% ---- Outputs ---------------------------------------------------------
obj.constraintAdjacencyMatrix = Acontrol;
obj.routingFlows = F;
obj.routingFractions = R;
obj.routingAdjacencyMatrix = F > 0;
obj.routingPotentials = phi;
obj.routingUnrouted = unrouted;
end
function cComp = bfs(subgraphAdjacency, startIdx)
% Breadth-first search returning the connected component of startIdx.
% Pre-allocated queue and component buffer with head/tail pointers to
% avoid element deletion and dynamic array growth (same pattern as the
% local bfs in lesserNeighbor.m).
n = size(subgraphAdjacency, 1);
visited = false(1, n);
queue = zeros(1, n);
cCompBuf = zeros(1, n);
qHead = 1;
qTail = 2;
queue(1) = startIdx;
cCompBuf(1) = startIdx;
cSize = 1;
visited(startIdx) = true;
while qHead < qTail
current = queue(qHead);
qHead = qHead + 1;
neighbors = find(subgraphAdjacency(current, :));
for kk = 1:numel(neighbors)
neighbor = neighbors(kk);
if ~visited(neighbor)
visited(neighbor) = true;
cCompBuf(cSize + 1) = neighbor;
cSize = cSize + 1;
queue(qTail) = neighbor;
qTail = qTail + 1;
end
end
end
cComp = sort(cCompBuf(1:cSize));
end