Files
miSim/@miSim/routeTopology.m
T
2026-07-12 22:50:14 -07:00

147 lines
5.8 KiB
Matlab

function obj = routeTopology(obj)
% ROUTETOPOLOGY Select the network topology by solving a min-max-workload
% routing LP (alternative to the lesser-neighbor algorithm).
%
% Every agent produces one unit of sensing data per timestep, all of which
% must reach one of the network endpoints (agents 1 and the largest odd
% index), which output data off-network for free (bottomless sinks). Data
% may split across paths, but relayed data accumulates at intermediate
% nodes, so each node experiences a workload: the share of a timestep it
% spends transmitting and receiving, with per-link effort flow/capacity and
% Shannon capacity c = log2(1 + SINR). The LP minimizes the maximum
% workload over all nodes, since the busiest node bounds system capacity.
%
% Candidate links are the threshold-feasible pairs in obj.adjacency (both
% directional SINRs clear sinrThreshold, per updateAdjacency). After the
% first solve, links carrying negligible flow (< routingFlowThreshold) are
% disallowed and the LP is re-solved so they cannot reappear: feasible but
% impractical links add system complexity for very little gain.
%
% The routed links are a HIGH-QUALITY OVERLAY on top of the low-level
% connectivity topology, not a replacement for it: basic command/telemetry
% traffic must always be routable between any two drones, so the caller
% runs lesserNeighbor first and this method ORs the bulk links into the
% maintained set. Both tiers are held above threshold by the CBF.
%
% Outputs (set on obj):
% routingAdjacencyMatrix directed adjacency, (i, j) = bulk link i -> j
% routingFlows flow carried by each directed link
% constraintAdjacencyMatrix existing (lesser-neighbor) links OR the
% symmetrized routed links (the CBF maintains
% both directions of every link)
%
% Uses linprog (Optimization Toolbox): MATLAB simulation only, no codegen.
arguments (Input)
obj (1, 1) {mustBeA(obj, "miSim")};
end
arguments (Output)
obj (1, 1) {mustBeA(obj, "miSim")};
end
nAgents = size(obj.agents, 1);
% Network endpoints: agent 1 and the largest ODD index (nAgents when
% odd, nAgents-1 when even); with one or two agents only agent 1.
endpoints = unique([1, 2 * ceil(nAgents / 2) - 1]);
isEndpoint = false(1, nAgents);
isEndpoint(endpoints) = true;
% Trivial network: every node is an endpoint, no bulk routing needed
% (the lesser-neighbor constraint topology is left untouched)
if all(isEndpoint)
obj.routingAdjacencyMatrix = false(nAgents);
obj.routingFlows = zeros(nAgents);
return;
end
positions = zeros(nAgents, 3);
for kk = 1:nAgents
positions(kk, :) = obj.agents{kk}.pos;
end
% Candidate directed links: threshold-feasible pairs, excluding
% endpoint-sourced links (endpoints only sink; allowing them to
% transmit only enables useless cycles). Capacity is the Shannon
% spectral efficiency of the transmission direction.
src = zeros(nAgents^2, 1);
dst = zeros(nAgents^2, 1);
cap = zeros(nAgents^2, 1);
nE = 0;
for ii = 1:nAgents
if isEndpoint(ii)
continue;
end
for jj = 1:nAgents
if jj == ii || ~obj.adjacency(ii, jj)
continue;
end
nE = nE + 1;
src(nE) = ii;
dst(nE) = jj;
cap(nE) = log2(1 + obj.sinrLink(positions, jj, ii)); % tx ii -> rx jj
end
end
src = src(1:nE);
dst = dst(1:nE);
cap = cap(1:nE);
% --- LP over x = [f_1 .. f_nE, W] ------------------------------------
% min W + eps*sum(f) (eps breaks ties toward sparse, cycle-free flow)
% s.t. flow conservation (per non-endpoint i): out(i) - in(i) = 1
% workload (per node i): sum(f_out/c) + sum(f_in/c) - W <= 0
% f >= 0
relays = find(~isEndpoint);
nRelays = numel(relays);
fObj = [1e-6 * ones(nE, 1); 1];
Aeq = zeros(nRelays, nE + 1);
for rr = 1:nRelays
Aeq(rr, 1:nE) = double(src == relays(rr))' - double(dst == relays(rr))';
end
beq = ones(nRelays, 1);
Aineq = zeros(nAgents, nE + 1);
for ii = 1:nAgents
Aineq(ii, 1:nE) = (double(src == ii)' + double(dst == ii)') ./ cap';
Aineq(ii, nE + 1) = -1;
end
bineq = zeros(nAgents, 1);
lb = zeros(nE + 1, 1);
options = optimoptions("linprog", "Display", "off");
[x, ~, exitflag] = linprog(fObj, Aineq, bineq, Aeq, beq, lb, [], options);
if exitflag <= 0
error("Routing LP infeasible: feasible links cannot route all data to an endpoint");
end
% Prune trivial links and re-solve with them disallowed, so negligible
% flows cannot reappear in the final solution
ub = inf(nE + 1, 1);
ub(x(1:nE) < obj.routingFlowThreshold) = 0;
[x, ~, exitflag] = linprog(fObj, Aineq, bineq, Aeq, beq, lb, ub, options);
if exitflag <= 0
error("Routing LP infeasible after pruning: routingFlowThreshold eliminated links needed to route all data to an endpoint");
end
% Extract the directed topology from the pruned solution
flows = x(1:nE);
obj.routingFlows = zeros(nAgents);
obj.routingAdjacencyMatrix = false(nAgents);
for ee = 1:nE
if flows(ee) > 1e-9
obj.routingFlows(src(ee), dst(ee)) = flows(ee);
obj.routingAdjacencyMatrix(src(ee), dst(ee)) = true;
end
end
% OR the bulk links into the low-level (lesser-neighbor) constraint
% topology already on obj: the CBF maintains both tiers, keeping the
% network connected for basic traffic while holding the routed links.
% Both directions of every link are maintained (feasibility requires
% both to clear the threshold anyway).
obj.constraintAdjacencyMatrix = obj.constraintAdjacencyMatrix | obj.routingAdjacencyMatrix | obj.routingAdjacencyMatrix';
end