1function pas_closed_tandem_fig6()
2% PAS_CLOSED_TANDEM_FIG6 Closed tandem of two pass-and-swap (PAS) queues,
3% reproducing Figures 5 and 6 of Comte & Dorsman,
"Pass-and-Swap Queues"
4% (2021, arXiv:2009.12299; Queueing Systems).
6% Topology (Fig. 6): --> PASQueue1 --> PASQueue2 --> (closed tandem)
8% There are I=6 customer
classes with a SINGLE customer of each
class. Both
9% queues share the swapping graph of Figure 5 (undirected;
classes as
nodes):
11% 6 edges: 1-3, 1-4, 2-4, 2-5, 3-6, 4-6, 5-6
12% / | \ (class 6 at the top,
classes 1,2 at the bottom).
17% As in the paper
's illustration, only the customer at the HEAD of each queue
18% receives a positive service rate (single server): mu(c)=mu1 at queue 1 and
19% nu(d)=mu2 at queue 2, independent of class so that the total rate depends
20% only on the customer multiset (order-independence). Upon a service
21% completion the departing customer is selected by the pass-and-swap scan over
22% the swapping graph and is routed to the back of the OTHER queue as a
23% customer of the same class.
25% The model is solved exactly by SolverCTMC and by the SolverLDES simulator,
26% and both are validated against an independent product-form reference that
27% builds the Markov chain directly from the pass-and-swap kernel (Theorem 5).
29% NOTE: a closed PAS network with a non-empty swapping graph has a reducible
30% generator (the pass-and-swap mechanism conserves the placement order, paper
31% Prop. 2/4), so the stationary distribution is that of the recurrent
32% component containing the initial state c=(1,2,3,4,5,6) of Figure 6a.
34mu1 = 1.0; mu2 = 1.3; % head-only single-server rates at queue 1, 2
35edges = [1 3; 1 4; 2 4; 2 5; 3 6; 4 6; 5 6];
37for k = 1:size(edges,1)
38 G(edges(k,1),edges(k,2)) = 1;
39 G(edges(k,2),edges(k,1)) = 1;
42% ---- LINE model -------------------------------------------------------
43model = Network('PASclosedTandem
');
44q1 = Queue(model, 'PASQueue1
', SchedStrategy.PAS);
45q2 = Queue(model, 'PASQueue2
', SchedStrategy.PAS);
48 jobclass{r} = ClosedClass(model, sprintf('Class%d
', r), 1, q1);
50q1.setService(@(c) mu1); % head-only: mu(prefix)=mu1 => Delta mu>0 only at head
51q2.setService(@(c) mu2);
52q1.setSwapGraph(G); q1.setNumberOfServers(1); q1.setCap(6);
53q2.setSwapGraph(G); q2.setNumberOfServers(1); q2.setCap(6);
54P = model.initRoutingMatrix;
56 P{jobclass{r}}(q1, q2) = 1.0;
57 P{jobclass{r}}(q2, q1) = 1.0;
61% A closed PAS network with a non-empty swapping graph is reducible (one
62% recurrent component per placement order), so the initial job placement is a
63% REQUIRED model input -- there is no valid default. We use the initial state
64% of Figure 6a: queue 1 = (1,2,3,4,5,6) (class 1 oldest, at the head), queue 2
65% empty. Solving with a reversed placement would select the mirror component.
66q1.setState([1 2 3 4 5 6]);
68fprintf('=== CTMC (exact) ===\n
');
69Tc = CTMC(model, 'cutoff
', 6).getAvgTable;
72fprintf('=== LDES (simulation, 6e5 samples) ===\n
');
73Tl = LDES(model, 'samples
', 6e5, 'seed
', 23000, 'verbose
', false).getAvgTable;
75% ---- independent product-form reference -------------------------------
76[Qref1, Qref2] = reference_pas_tandem(G, [mu1 mu2]);
77Qref = [Qref1, Qref2]'; % column order: q1
classes 1..6 then q2
classes 1..6
79qc = Tc.QLen; ql = Tl.QLen;
80fprintf(
'\n station class reference CTMC LDES\n');
82 fprintf(
' %-9s %-6s %9.5f %9.5f %9.5f\n',
string(Tc.Station(i)), ...
83 string(Tc.JobClass(i)), Qref(i), qc(i), ql(i));
85errCTMC = max(abs(qc - Qref));
86errLDES = max(abs(ql - Qref));
87fprintf(
'\nCTMC vs reference: max|dQ| = %.3e\n', errCTMC);
88fprintf(
'LDES vs reference: max|dQ| = %.3e (simulation noise)\n', errLDES);
89assert(errCTMC <= 1e-9,
'CTMC does not match the pass-and-swap reference');
90assert(errLDES <= 1e-2,
'LDES does not match the pass-and-swap reference');
91fprintf(
'PASS: CTMC matches the pass-and-swap reference; LDES agrees within noise.\n');
94% =======================================================================
95function [Q1, Q2] = reference_pas_tandem(G, mu)
96% Independent reference: build the CTMC of the closed PAS tandem directly from
97% the pass-and-swap kernel, by reachability from the initial state of Fig. 6a
98% (queue 1 = (1,...,6) ascending, queue 2 empty), head-only service.
101states = {}; idx = containers.Map(
'KeyType',
'char',
'ValueType',
'double');
102 function
id = getid(l1, l2)
103 key = [sprintf(
'%d,', l1),
'|', sprintf(
'%d,', l2)];
107 id = numel(states) + 1; idx(key) = id; states{
id} = {l1, l2};
111fr = 1; E = zeros(0,3);
112while fr <= numel(states)
113 l1 = states{fr}{1}; l2 = states{fr}{2};
114 if ~isempty(l1) % head of queue 1 completes
115 [nl, dep] = pskernel(l1, 1, G);
116 j = getid(nl, [l2, dep]); E(end+1,:) = [fr, j, mu(1)]; %#ok<AGROW>
118 if ~isempty(l2) % head of queue 2 completes
119 [nl, dep] = pskernel(l2, 1, G);
120 j = getid([l1, dep], nl); E(end+1,:) = [fr, j, mu(2)]; %#ok<AGROW>
124ns = numel(states); Qm = zeros(ns);
126 Qm(E(e,1),E(e,2)) = Qm(E(e,1),E(e,2)) + E(e,3);
128for i = 1:ns, Qm(i,i) = -sum(Qm(i,:)); end
129pivec = ([Qm'; ones(1,ns)] \ [zeros(ns,1); 1])';
130Q1 = zeros(1,n); Q2 = zeros(1,n);
132 l1 = states{i}{1}; l2 = states{i}{2};
134 Q1(r) = Q1(r) + pivec(i)*sum(l1==r);
135 Q2(r) = Q2(r) + pivec(i)*sum(l2==r);
140% =======================================================================
141function [newlist, dep] = pskernel(list, p, G)
142% Pass-and-swap kernel: the customer at position p completes; it scans toward
143% the back
for the first swappable
class, takes its place, the ejected customer
144% repeats, and the last ejected customer departs. Classes shift one step along
145% the chain and the served slot
is removed.
146nn = numel(list); chain = p; moving = list(p); cur = p;
150 if G(moving, list(j)), q = j;
break; end
152 if q == 0,
break; end
153 chain(end+1) = q; moving = list(q); cur = q; %#ok<AGROW>
155dep = list(chain(end));
157for i = 1:numel(chain)-1
158 newlist(chain(i+1)) = list(chain(i));
160newlist(chain(1)) = [];