LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
pas_cyclic_ctmc_vs_bruteforce.m
1function pas_cyclic_ctmc_vs_bruteforce()
2% PAS_CYCLIC_CTMC_VS_BRUTEFORCE Validate LINE's CTMC solver on a closed
3% cyclic network of two pass-and-swap (PAS) queues against the exact
4% product-form (order-independent) brute-force enumeration.
5%
6% Topology: --> Queue1 --> Queue2 --> (cyclic, R closed classes)
7%
8% PAS queues are order-independent, so the network is product-form. The
9% brute force enumerates the whole ordered state space to obtain the
10% normalizing constant G, hence exact per-class throughputs and mean
11% queue lengths, which are compared against SolverCTMC.
12
13if exist('Network','class') ~= 8
14 lineStart;
15end
16
17% ---- shared model parameters ------------------------------------------
18% Both stations must be valid order-independent (OI) queues: the TOTAL
19% service rate mu_i(c) depends only on the customer multiset.
20% station 1: M/M/k OI queue, class-independent -> mu1(c) = min(n,k1)*s1
21% station 2: infinite-server, class-dependent -> mu2(c) = sum_j beta2(c_j)
22K = [2 2]; % per-class closed populations
23s1 = 1.0; k1 = 2; % station 1: 2-server OI (M/M/2)
24beta2 = [1.5 1.0]; % station 2: per-class infinite-server rates
25R = numel(K);
26
27mu1 = @(c) min(numel(c(c>0)), k1) * s1;
28mu2 = @(c) sum(beta2(c(c>0)));
29
30% ---- (1) brute-force product-form reference ---------------------------
31[Gbf, Xbf, Qbf] = bruteforce(K, mu1, mu2);
32
33fprintf('Brute-force normalizing constant G = %.12g\n\n', Gbf);
34
35% ---- (2) LINE closed PAS network solved by CTMC -----------------------
36model = Network('PAScyclic');
37q1 = Queue(model, 'PASQueue1', SchedStrategy.PAS);
38q2 = Queue(model, 'PASQueue2', SchedStrategy.PAS);
39
40jobclass = cell(1,R);
41for r = 1:R
42 jobclass{r} = ClosedClass(model, sprintf('Class%d', r), K(r), q1);
43end
44
45% Same total service-rate functions as the brute force; empty swap graph
46% (plain order-independent queue -- result is swap-graph invariant).
47q1.setService(mu1);
48q2.setService(mu2);
49q1.setSwapGraph(zeros(R)); q1.setNumberOfServers(k1); q1.setCap(sum(K));
50q2.setSwapGraph(zeros(R)); q2.setNumberOfServers(sum(K)); q2.setCap(sum(K));
51
52% Cyclic routing q1 -> q2 -> q1 for every class.
53P = model.initRoutingMatrix;
54for r = 1:R
55 P{jobclass{r}}(q1, q2) = 1.0;
56 P{jobclass{r}}(q2, q1) = 1.0;
57end
58model.link(P);
59
60T = CTMC(model, 'cutoff', sum(K)).getAvgTable;
61disp(T);
62
63% ---- (3) compare ------------------------------------------------------
64% Cyclic network with one visit per station => per-station per-class
65% throughput equals the chain throughput X_r; mean queue length per
66% (station,class) is Q_{i,r}.
67Xctmc = zeros(1,R); Qctmc = zeros(2,R);
68stationName = {'PASQueue1','PASQueue2'};
69for r = 1:R
70 cname = sprintf('Class%d', r);
71 for i = 1:2
72 row = strcmp(string(T.Station), stationName{i}) & ...
73 strcmp(string(T.JobClass), cname);
74 Qctmc(i,r) = T.QLen(row);
75 if i == 1
76 Xctmc(r) = T.Tput(row);
77 end
78 end
79end
80
81fprintf('\n--- Throughput (per class) ---\n');
82fprintf('%-8s %14s %14s %10s\n', 'class', 'brute-force', 'CTMC', 'abs.err');
83for r = 1:R
84 fprintf('Class%-3d %14.10f %14.10f %10.2e\n', ...
85 r, Xbf(r), Xctmc(r), abs(Xbf(r)-Xctmc(r)));
86end
87
88fprintf('\n--- Mean queue length (station, class) ---\n');
89fprintf('%-10s %-8s %14s %14s %10s\n', 'station','class','brute-force','CTMC','abs.err');
90for i = 1:2
91 for r = 1:R
92 fprintf('%-10s Class%-3d %14.10f %14.10f %10.2e\n', ...
93 stationName{i}, r, Qbf(i,r), Qctmc(i,r), abs(Qbf(i,r)-Qctmc(i,r)));
94 end
95end
96
97tol = 1e-9;
98errX = max(abs(Xbf - Xctmc));
99errQ = max(abs(Qbf(:) - Qctmc(:)));
100fprintf('\nCTMC: max|dX| = %.3e max|dQ| = %.3e\n', errX, errQ);
101assert(errX <= tol && errQ <= tol, ...
102 'CTMC vs brute-force mismatch: max|dX|=%.3e max|dQ|=%.3e', errX, errQ);
103fprintf('PASS: CTMC matches brute-force product form within %.1e.\n', tol);
104
105% ---- (4) LDES discrete-event simulation (matches within sim noise) ----
106% The same closed PAS network solved by the LINE Discrete Event Simulator.
107% LDES is a stochastic simulator, so agreement is expected only to within
108% simulation noise (Monte-Carlo confidence interval), not to machine eps.
109Tl = LDES(model, 'samples', 2e5, 'seed', 23000, 'verbose', false).getAvgTable;
110Xldes = zeros(1,R); Qldes = zeros(2,R);
111for r = 1:R
112 cname = sprintf('Class%d', r);
113 for i = 1:2
114 row = strcmp(string(Tl.Station), stationName{i}) & ...
115 strcmp(string(Tl.JobClass), cname);
116 Qldes(i,r) = Tl.QLen(row);
117 if i == 1
118 Xldes(r) = Tl.Tput(row);
119 end
120 end
121end
122
123fprintf('\n--- LDES vs brute-force (simulation, 2e5 samples) ---\n');
124fprintf('%-12s %-8s %14s %14s %10s\n','metric','class','brute-force','LDES','rel.err');
125for r = 1:R
126 fprintf('Tput Class%-3d %14.6f %14.6f %9.2f%%\n', ...
127 r, Xbf(r), Xldes(r), 100*abs(Xbf(r)-Xldes(r))/Xbf(r));
128end
129for i = 1:2
130 for r = 1:R
131 fprintf('QLen %-7s Class%-3d %14.6f %14.6f %9.2f%%\n', ...
132 stationName{i}, r, Qbf(i,r), Qldes(i,r), 100*abs(Qbf(i,r)-Qldes(i,r))/Qbf(i,r));
133 end
134end
135
136tolSim = 0.02; % 2% relative tolerance (simulation)
137relX = max(abs(Xbf - Xldes) ./ Xbf);
138relQ = max(abs(Qbf(:) - Qldes(:)) ./ Qbf(:));
139fprintf('\nLDES: max rel|dX| = %.2f%% max rel|dQ| = %.2f%%\n', 100*relX, 100*relQ);
140assert(relX <= tolSim && relQ <= tolSim, ...
141 'LDES vs brute-force exceeds %.0f%%: relX=%.2f%% relQ=%.2f%%', ...
142 100*tolSim, 100*relX, 100*relQ);
143fprintf('PASS: LDES matches brute-force within %.0f%% (simulation noise).\n', 100*tolSim);
144end
145
146% =======================================================================
147function [G, X, Q] = bruteforce(K, mu1, mu2)
148% Exact product-form normalizing constant G, per-class throughputs X
149% (X_r = G(K-1_r)/G(K)), and mean queue lengths Q(i,r) by full ordered
150% state-space enumeration of the two order-independent stations.
151R = numel(K);
152e1 = ones(1,R); e2 = ones(1,R);
153G = norm_const(K, e1, e2, mu1, mu2);
154
155X = zeros(1,R);
156for r = 1:R
157 Km = K; Km(r) = Km(r) - 1;
158 X(r) = norm_const(Km, e1, e2, mu1, mu2) / G; % e_r = 1
159end
160
161% Q(1,r) = (1/G) sum_m m_r S1(m) S2(K-m); Q(2,r) = K_r - Q(1,r).
162Q = zeros(2,R);
163splits = enum_splits(K);
164for s = 1:size(splits,1)
165 m = splits(s,:);
166 S1 = oi_sum(m, e1, mu1);
167 S2 = oi_sum(K - m, e2, mu2);
168 Q(1,:) = Q(1,:) + m .* (S1 * S2);
169end
170Q(1,:) = Q(1,:) / G;
171Q(2,:) = K - Q(1,:);
172end
173
174function G = norm_const(K, e1, e2, mu1, mu2)
175G = 0;
176splits = enum_splits(K);
177for s = 1:size(splits,1)
178 m = splits(s,:);
179 G = G + oi_sum(m, e1, mu1) * oi_sum(K - m, e2, mu2);
180end
181end
182
183function S = oi_sum(counts, e, mu)
184S = oi_dfs(counts, [], 1.0, e, mu);
185end
186
187function S = oi_dfs(counts, prefix, w, e, mu)
188if all(counts == 0)
189 S = w; return;
190end
191S = 0;
192for r = 1:numel(counts)
193 if counts(r) > 0
194 c2 = counts; c2(r) = c2(r) - 1;
195 p2 = [prefix, r];
196 S = S + oi_dfs(c2, p2, w * e(r) / mu(p2), e, mu);
197 end
198end
199end
200
201function m = pas_rate(c, beta, k)
202% Total OI service rate of the ordered prefix c (zero-padded, 1-based
203% class ids) for a k-server PAS station.
204cc = c(c > 0);
205n = numel(cc);
206if n == 0
207 m = 0; return;
208end
209served = cc(1:min(n,k));
210m = sum(beta(served));
211end
212
213function splits = enum_splits(K)
214R = numel(K);
215nrows = prod(K + 1);
216splits = zeros(nrows, R);
217idx = zeros(1, R);
218for s = 1:nrows
219 splits(s,:) = idx;
220 for r = 1:R
221 if idx(r) < K(r)
222 idx(r) = idx(r) + 1; break;
223 else
224 idx(r) = 0;
225 end
226 end
227end
228end