1function H = pas_swap2order(swap, listRate, N0)
2% H = PAS_SWAP2ORDER(SWAP, LISTRATE, N0)
4% Derive the GLOBAL placement-order DAG H of a closed two-station pass-and-swap
5% (
P&S) tandem 1->2->1 directly from its swap graph,
for use with PFQN_PAS_IS.
7% With a non-empty swap graph the ordered-state chain
is reducible (Comte &
8% Dorsman, 2021, arXiv:2009.12299): the recurrent communicating class
is the set
9% of splits (c_{1..k}; c_{ell..k+1}) of the orderings c that are the linear
10% extensions of a single placement partial order on the classes (their Prop.).
11% PFQN_PAS_IS samples those orderings from H, so it needs exactly
this global
12% order -- NOT the per-station placement orders consumed by the exact
13% convolution PFQN_PAS_NC (which carry the same information one station at a
14% time, transposed around the cycle, and are individually over-constrained
for
15% the single-order IS formulation). Feed H to PAS_PLACEMENT to obtain the
16% precedence closure that PFQN_PAS_NC expects.
18% The placement order
is a
class-level property, independent of the per-
class
19% multiplicity, so it
is extracted from the minimal single-job-per-
class
20% instance (N0 = ones(1,R)): enumerate the reachable communicating class from
21% the all-in-queue-1 initial state; each reachable state (l1;l2) exposes the
22% full ordering c = [l1, reverse(l2)] in D; then set H(i,j)=1 iff
class i
23% precedes
class j in EVERY c in D (forced precedence). Incomparable pairs are
24% left 0 (antichain). The result
is valid
for any population N.
27% swap - (R x R) swap graph, or cell {G1,G2} of the two per-queue graphs;
28% G(a,b)~=0 means
class a chases class b. Empty/all-zero => plain
29% OI: H = 0 (every ordering feasible).
30% listRate - cell {1 x 2} of OI microstate rate functions; listRate{m}(c)
is
31% the total service rate of queue m on the ordered prefix c. Used
32% to prune zero-rate (non-head) completions.
33% N0 - (1 x R) minimal probing population; defaults to ones(1,R).
36% H - (R x R) global placement-order DAG; H(i,j)=1 iff i must precede j.
38% See also PFQN_PAS_IS, PAS_PLACEMENT, PFQN_PAS_NC.
40% Copyright (c) 2012-2026, Imperial College London
44if ~iscell(swap), swap = repmat({swap}, 1, M); end
45if nargin < 3 || isempty(N0)
51if (isempty(swap{1}) || ~any(swap{1}(:) ~= 0)) && ...
52 (isempty(swap{2}) || ~any(swap{2}(:) ~= 0))
53 H = zeros(R, R); % pure OI: no placement constraint
57% minimal single-job-per-
class initial placement, all jobs at queue 1
59for r = 1:R, initState = [initState, repmat(r, 1, N0(r))]; end %#ok<AGROW>
60init = {initState, []};
62% breadth-first enumeration of the reachable (communicating)
class
63key = containers.Map(
'KeyType',
'char',
'ValueType',
'logical');
64classStates = {}; frontier = {init}; key(pas_enc(init)) =
true;
65while ~isempty(frontier)
66 st = frontier{end}; frontier(end) = [];
67 classStates{end+1} = st; %#ok<AGROW>
69 c = st{m}; next = mod(m, M) + 1;
71 % Marginal OI completion rate of the p-th customer. The empty-prefix
72 % rate
is 0 by definition (no jobs -> no service); some svcRateFun
73 % handles
return a nonzero constant on [], so guard p==1 explicitly.
77 prevRate = listRate{m}(c(1:p-1));
79 rate = listRate{m}(c(1:p)) - prevRate;
80 if rate <= 1e-12,
continue; end
81 [cnew, dep] = pas_swap_local(c, p, swap{m});
82 stn = st; stn{m} = cnew; stn{next} = [st{next}, dep];
84 if ~isKey(key, k), key(k) =
true; frontier{end+1} = stn; end %#ok<AGROW>
89% full orderings D: every reachable state (l1;l2) exposes c = [l1, reverse(l2)]
90Dmap = containers.Map(
'KeyType',
'char',
'ValueType',
'logical');
92for s = 1:numel(classStates)
93 c = [classStates{s}{1}, fliplr(classStates{s}{2})];
94 kc = sprintf(
'%d,', c);
95 if ~isKey(Dmap, kc), Dmap(kc) =
true; D{end+1} = c; end %#ok<AGROW>
98% forced precedence: i precedes j iff in every c in D, every copy of i
is older
99% than every copy of j (position-wise), whenever both are present.
103 if a == b,
continue; end
104 both =
false; forced =
true;
107 pa = find(c == a); pb = find(c == b);
108 if isempty(pa) || isempty(pb),
continue; end
110 if ~(max(pa) < min(pb)), forced =
false;
break; end
112 H(a, b) = both && forced;
118% ------------------------------------------------------------------------
119function s = pas_enc(st)
120parts = cell(1, numel(st));
121for m = 1:numel(st), parts{m} = sprintf(
'%d,', st{m}); end
122s = strjoin(parts,
'|');
125function [cnew, dep] = pas_swap_local(c, p, G)
126n = numel(c); chain = p; moving = c(p); cur = p;
129 for j = cur+1:n,
if ~isempty(G) && G(moving, c(j)) ~= 0, q = j;
break; end; end
130 if q == -1,
break; end
131 chain(end+1) = q; moving = c(q); cur = q; %#ok<AGROW>
133dep = c(chain(end)); tmp = c;
134for i = 1:numel(chain)-1, tmp(chain(i+1)) = c(chain(i)); end
135tmp(chain(1)) = []; cnew = tmp;