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-queue posets of PAS_SWAP2PREC (which, combined pairwise
13% in
the exact convolution PFQN_PASNC, encode
the same information differently
14% and are individually over-constrained
for the single-order IS formulation).
16% The placement order
is a
class-level property, independent of
the per-
class
17% multiplicity, so it
is extracted from
the minimal single-job-per-
class
18% instance (N0 = ones(1,R)): enumerate
the reachable communicating class from
19%
the all-in-queue-1 initial state; each reachable state (l1;l2) exposes
the
20% full ordering c = [l1, reverse(l2)] in D; then set H(i,j)=1 iff
class i
21% precedes
class j in EVERY c in D (forced precedence). Incomparable pairs are
22% left 0 (antichain). The result
is valid
for any population N.
25% swap - (R x R) swap graph, or cell {G1,G2} of
the two per-queue graphs;
26% G(a,b)~=0 means
class a chases class b. Empty/all-zero => plain
27% OI: H = 0 (every ordering feasible).
28% listRate - cell {1 x 2} of OI microstate rate functions; listRate{m}(c)
is
29%
the total service rate of queue m on
the ordered prefix c. Used
30% to prune zero-rate (non-head) completions.
31% N0 - (1 x R) minimal probing population; defaults to ones(1,R).
34% H - (R x R) global placement-order DAG; H(i,j)=1 iff i must precede j.
36% See also PFQN_PAS_IS, PAS_PLACEMENT, PFQN_PASNC.
38% Copyright (c) 2012-2026, Imperial College London
42if ~iscell(swap), swap = repmat({swap}, 1, M); end
43if nargin < 3 || isempty(N0)
49if (isempty(swap{1}) || ~any(swap{1}(:) ~= 0)) && ...
50 (isempty(swap{2}) || ~any(swap{2}(:) ~= 0))
51 H = zeros(R, R); % pure OI: no placement constraint
55% minimal single-job-per-
class initial placement, all jobs at queue 1
57for r = 1:R, initState = [initState, repmat(r, 1, N0(r))]; end %#ok<AGROW>
58init = {initState, []};
60% breadth-first enumeration of
the reachable (communicating)
class
61key = containers.Map(
'KeyType',
'char',
'ValueType',
'logical');
62classStates = {}; frontier = {init}; key(pas_enc(init)) =
true;
63while ~isempty(frontier)
64 st = frontier{end}; frontier(end) = [];
65 classStates{end+1} = st; %#ok<AGROW>
67 c = st{m}; next = mod(m, M) + 1;
69 % Marginal OI completion rate of
the p-th customer. The empty-prefix
70 % rate
is 0 by definition (no jobs -> no service); some svcRateFun
71 % handles
return a nonzero constant on [], so guard p==1
explicitly.
75 prevRate = listRate{m}(c(1:p-1));
77 rate = listRate{m}(c(1:p)) - prevRate;
78 if rate <= 1e-12,
continue; end
79 [cnew, dep] = pas_swap_local(c, p, swap{m});
80 stn = st; stn{m} = cnew; stn{next} = [st{next}, dep];
82 if ~isKey(key, k), key(k) =
true; frontier{end+1} = stn; end %#ok<AGROW>
87% full orderings D: every reachable state (l1;l2) exposes c = [l1, reverse(l2)]
88Dmap = containers.Map(
'KeyType',
'char',
'ValueType',
'logical');
90for s = 1:numel(classStates)
91 c = [classStates{s}{1}, fliplr(classStates{s}{2})];
92 kc = sprintf(
'%d,', c);
93 if ~isKey(Dmap, kc), Dmap(kc) =
true; D{end+1} = c; end %#ok<AGROW>
96% forced precedence: i precedes j iff in every c in D, every copy of i
is older
97% than every copy of j (position-wise), whenever both are present.
101 if a == b,
continue; end
102 both =
false; forced =
true;
105 pa = find(c == a); pb = find(c == b);
106 if isempty(pa) || isempty(pb),
continue; end
108 if ~(max(pa) < min(pb)), forced =
false;
break; end
110 H(a, b) = both && forced;
116% ------------------------------------------------------------------------
117function s = pas_enc(st)
118parts = cell(1, numel(st));
119for m = 1:numel(st), parts{m} = sprintf(
'%d,', st{m}); end
120s = strjoin(parts,
'|');
123function [cnew, dep] = pas_swap_local(c, p, G)
124n = numel(c); chain = p; moving = c(p); cur = p;
127 for j = cur+1:n,
if ~isempty(G) && G(moving, c(j)) ~= 0, q = j;
break; end; end
128 if q == -1,
break; end
129 chain(end+1) = q; moving = c(q); cur = q; %#ok<AGROW>
131dep = c(chain(end)); tmp = c;
132for i = 1:numel(chain)-1, tmp(chain(i+1)) = c(chain(i)); end
133tmp(chain(1)) = []; cnew = tmp;