LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
pas_cyclic_normconst_bruteforce.m
1function G = pas_cyclic_normconst_bruteforce()
2% PAS_CYCLIC_NORMCONST_BRUTEFORCE Brute-force normalizing constant of a
3% closed cyclic network of two pass-and-swap (PAS) queues.
4%
5% Topology: --> Queue1 --> Queue2 --> (cyclic, R closed classes)
6%
7% A pass-and-swap queue (Dorsman & Gardner 2024, Queueing Syst.
8% 107:205-256) is an order-independent (OI) station: its total service
9% rate is a function mu_i(c) of the ordered class sequence c at the
10% station, and the stationary law is product-form and INVARIANT to the
11% swap graph. Hence the swap graph is irrelevant to the normalizing
12% constant and is not needed here.
13%
14% For a closed network of OI/PAS stations the unnormalized stationary
15% weight of a global ordered state (c1,c2) factorizes as
16%
17% w(c1,c2) = Phi_1(c1) * Phi_2(c2),
18% Phi_i(c) = prod_{j=1..n} e_{i,c_j} / mu_i(c_1,...,c_j),
19%
20% where e_{i,r} is the per-class visit ratio (relative arrival rate at
21% station i) and mu_i(prefix) is the total service rate of the ordered
22% prefix. The normalizing constant is the exact sum over the whole state
23% space,
24%
25% G = sum_{(c1,c2)} Phi_1(c1) * Phi_2(c2)
26% = sum_{m<=K} S_1(m) * S_2(K-m),
27%
28% obtained by (i) splitting the population vector K into the per-class
29% counts m at station 1 and K-m at station 2, and (ii) summing the OI
30% balance function over every ordered arrangement (multiset permutation)
31% of each station's count vector. S_i(n) is that per-station sum.
32%
33% Returns the scalar normalizing constant G.
34
35% ---- model parameters --------------------------------------------------
36K = [2 2]; % per-class closed populations (R classes)
37R = numel(K);
38
39% Visit ratios. Cyclic two-queue with no class switching => every class
40% visits each station once per cycle, so e1 = e2 = 1 (any common scaling
41% of e cancels in normalized metrics).
42e1 = ones(1,R);
43e2 = ones(1,R);
44
45% Total OI service-rate functions mu_i(c) of the ordered prefix c (row
46% vector of 1-based class ids, no padding). ORDER-INDEPENDENCE requires
47% the TOTAL rate to depend only on the customer multiset; a single-server
48% class-dependent head rate would violate this and is NOT a valid PAS/OI
49% queue. Two valid choices used here:
50% station 1: M/M/k OI queue, class-independent -> mu1(c) = min(n,k1)*s1
51% station 2: infinite-server, class-dependent -> mu2(c) = sum_j beta2(c_j)
52% For station 2 the prefix sums depend on order, so the ordered-state
53% enumeration is genuinely needed (it does not collapse to class counts).
54s1 = 1.0; k1 = 2; % station 1: 2-server OI (M/M/2)
55beta2 = [1.5 1.0]; % station 2: per-class infinite-server rates
56mu1 = @(c) min(numel(c(c>0)), k1) * s1;
57mu2 = @(c) sum(beta2(c(c>0)));
58
59% ---- brute-force enumeration ------------------------------------------
60G = 0;
61splits = enum_splits(K); % every per-class split m (0<=m<=K)
62for s = 1:size(splits,1)
63 m = splits(s,:);
64 S1 = oi_sum(m, e1, mu1); % sum over orderings at station 1
65 S2 = oi_sum(K - m, e2, mu2); % sum over orderings at station 2
66 G = G + S1 * S2;
67end
68
69fprintf('Closed cyclic PAS network: R=%d classes, population K=[%s]\n', ...
70 R, strtrim(sprintf('%d ', K)));
71fprintf('Brute-force normalizing constant G = %.15g\n', G);
72
73% ---- per-class throughputs from the normalizing constant --------------
74% For product-form closed networks the chain throughput satisfies
75% X_r = e_r * G(K - 1_r) / G(K). With e_r = 1 this is just the ratio of
76% normalizing constants at populations K-1_r and K.
77X = zeros(1,R);
78for r = 1:R
79 if K(r) > 0
80 Km = K; Km(r) = Km(r) - 1;
81 X(r) = norm_const(Km, e1, e2, mu1, mu2) / G;
82 end
83end
84fprintf('Per-class throughput X = [%s]\n', strtrim(sprintf('%.6g ', X)));
85
86% ---- self-test: single-class reduction to Gordon-Newell ---------------
87selftest_single_class();
88end
89
90% =======================================================================
91function G = norm_const(K, e1, e2, mu1, mu2)
92% Normalizing constant for arbitrary population K (used for X = G/G).
93G = 0;
94splits = enum_splits(K);
95for s = 1:size(splits,1)
96 m = splits(s,:);
97 G = G + oi_sum(m, e1, mu1) * oi_sum(K - m, e2, mu2);
98end
99end
100
101% =======================================================================
102function S = oi_sum(counts, e, mu)
103% Sum of the OI balance function over every ordered arrangement
104% (distinct multiset permutation) of the per-class multiset `counts`.
105% Identical-class jobs yield a single ordered state, so we branch on the
106% next CLASS placed, generating each distinct sequence exactly once.
107S = oi_dfs(counts, [], 1.0, e, mu);
108end
109
110function S = oi_dfs(counts, prefix, w, e, mu)
111if all(counts == 0)
112 S = w; % completed an ordered state
113 return;
114end
115S = 0;
116for r = 1:numel(counts)
117 if counts(r) > 0
118 c2 = counts; c2(r) = c2(r) - 1;
119 p2 = [prefix, r];
120 w2 = w * e(r) / mu(p2); % append class r, accrue e_r/mu(prefix)
121 S = S + oi_dfs(c2, p2, w2, e, mu);
122 end
123end
124end
125
126% =======================================================================
127function m = pas_rate(c, beta, k)
128% Total OI service rate of the ordered prefix c (1-based class ids) for a
129% k-server PAS station: sum of base rates of the first min(n,k) positions.
130n = numel(c);
131if n == 0
132 m = 0;
133 return;
134end
135served = c(1:min(n,k));
136m = sum(beta(served));
137end
138
139% =======================================================================
140function splits = enum_splits(K)
141% Enumerate every per-class count vector m with 0 <= m_r <= K_r as rows
142% (odometer over the box [0,K_1] x ... x [0,K_R]).
143R = numel(K);
144nrows = prod(K + 1);
145splits = zeros(nrows, R);
146idx = zeros(1, R);
147for s = 1:nrows
148 splits(s,:) = idx;
149 for r = 1:R % increment the mixed-radix odometer
150 if idx(r) < K(r)
151 idx(r) = idx(r) + 1;
152 break;
153 else
154 idx(r) = 0;
155 end
156 end
157end
158end
159
160% =======================================================================
161function selftest_single_class()
162% Single class, two single-server PAS stations reduces to a closed
163% two-queue Gordon-Newell network whose normalizing constant is the
164% geometric sum G = sum_{n=0..N} (1/mu1)^n (1/mu2)^(N-n). Verify the
165% brute-force enumeration reproduces it.
166N = 5;
167mu1 = 3.0; mu2 = 2.0;
168e1 = 1; e2 = 1;
169muf1 = @(c) pas_rate(c, mu1, 1);
170muf2 = @(c) pas_rate(c, mu2, 1);
171Gbf = norm_const(N, e1, e2, muf1, muf2);
172Gcf = sum((1/mu1).^(0:N) .* (1/mu2).^(N:-1:0));
173assert(abs(Gbf - Gcf) <= 1e-12 * Gcf, ...
174 'self-test failed: brute-force %.15g vs closed-form %.15g', Gbf, Gcf);
175fprintf('Self-test (single-class Gordon-Newell): PASS (G=%.12g)\n', Gbf);
176end