new routing algorithm
This commit is contained in:
1 parent
37598479cc
commit
acfb2719e2
11 files changed
+671
-351
No files matched your search
@@ -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
|
||||
Reference in new issue
Block a user