LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
me_cqn.m
1function [L, W, Ca, Cd, lambda, rho, X, iter] = me_cqn(M, R, N, mu, Cs, P, c, refstat, insens, options)
2%ME_CQN Maximum Entropy algorithm for Closed Queueing Networks
3%
4% Implements the two-stage ME algorithm from Kouvatsos (1994) "Entropy
5% Maximisation and Queueing Network Models", Section 3.3, for closed
6% multiclass networks of G/G/1 and G/G/inf queues.
7%
8% Stage 1 solves a pseudo-open network (no external arrivals) subject to
9% job flow conservation and the population constraints sum_i L(i,r)=N(r),
10% using the GE-type fixed point of the open algorithm (Section 3.2).
11% Stage 2 builds the ME product-form solution (3.8) from the Lagrangian
12% coefficients of Stage 1, computes the normalising constant Z(N) by a
13% multiclass convolution, and iterates the flow (work rate) equations
14% until the class throughputs implied by the closed ME solution agree
15% with those used to parametrise the building blocks.
16%
17% INPUTS:
18% M - Number of queues (stations)
19% R - Number of job classes
20% N - Class populations [1 x R]
21% mu - Service rates [M x R matrix]
22% Cs - Service scv [M x R matrix]
23% P - Routing probability matrix [M x M x R], P(j,i,r) = p_ji,r
24% c - (optional) Servers per queue [M x 1]; Inf marks an
25% infinite-server (IS) queue; finite values must be 1
26% (default: ones(M,1))
27% refstat - (optional) Reference station per class [1 x R] for visit
28% normalisation (default: first station visited by the class)
29% options - (optional) struct with fields:
30% .tol - convergence tolerance (default: 1e-6)
31% .maxiter - maximum iterations (default: 1000)
32% .verbose - print iteration info (default: false)
33%
34% OUTPUTS:
35% L - Mean queue lengths [M x R matrix] (sum_i L(i,r) = N(r))
36% W - Mean response times [M x R matrix], W = L ./ lambda
37% Ca - Arrival scv at each queue [M x R matrix] (pseudo-open)
38% Cd - Departure scv at each queue [M x R matrix] (pseudo-open)
39% lambda - Class throughputs at each queue [M x R], lambda(i,r)=X(r)*v(i,r)
40% rho - Utilizations [M x R matrix] from the closed ME solution
41% (mean jobs in service for IS queues)
42% X - Class throughputs at the reference station [1 x R]
43% iter - Total number of fixed-point iterations
44%
45% Reference:
46% D.D. Kouvatsos, "Entropy Maximisation and Queueing Network Models",
47% Annals of Operations Research, 48:63-126, 1994. Section 3.3.
48
49if nargin < 7 || isempty(c)
50 c = ones(M, 1);
51end
52if nargin < 8 || isempty(refstat)
53 refstat = zeros(1, R);
54end
55if nargin < 9 || isempty(insens)
56 insens = false(M, 1);
57end
58if nargin < 10
59 options = struct();
60end
61insens = logical(insens(:));
62if ~isfield(options, 'tol')
63 options.tol = 1e-6;
64end
65if ~isfield(options, 'maxiter')
66 options.maxiter = 1000;
67end
68if ~isfield(options, 'verbose')
69 options.verbose = false;
70end
71c = c(:);
72N = N(:)';
73
74% Feedback correction (as in the open algorithm)
75P_eff = P;
76mu_eff = mu;
77Cs_eff = Cs;
78selfp = zeros(M, R);
79for i = 1:M
80 for r = 1:R
81 pii = P(i, i, r);
82 if pii > 0
83 selfp(i, r) = pii;
84 mu_eff(i, r) = mu(i, r) * (1 - pii);
85 Cs_eff(i, r) = pii + (1 - pii) * Cs(i, r);
86 P_eff(i, :, r) = P(i, :, r) / (1 - pii);
87 P_eff(i, i, r) = 0;
88 end
89 end
90end
91
92% Visit ratios from the original routing (visit-inclusive), normalised at
93% the reference station of each class
94V = zeros(M, R);
95for r = 1:R
96 A = eye(M) - P(:, :, r)';
97 if refstat(r) <= 0
98 ref = find(mu(:, r) > 0, 1);
99 else
100 ref = refstat(r);
101 end
102 A(ref, :) = 0;
103 A(ref, ref) = 1;
104 b = zeros(M, 1);
105 b(ref) = 1;
106 V(:, r) = A \ b;
107 V(abs(V(:, r)) < 1e-14, r) = 0;
108 refstat(r) = ref;
109end
110
111% Stage 1: pseudo-open network, find X such that sum_i L(i,r) = N(r)
112% Initialise throughputs at half the single-station capacity bound
113X = zeros(1, R);
114for r = 1:R
115 capr = Inf;
116 for i = 1:M
117 if ~isinf(c(i)) && V(i, r) > 0 && mu(i, r) > 0
118 capr = min(capr, mu(i, r) / V(i, r));
119 end
120 end
121 if isinf(capr) % IS-only class
122 capr = 1;
123 end
124 X(r) = 0.5 * capr / R;
125end
126
127Ca = ones(M, R);
128iter = 0;
129Lpo = zeros(M, R);
130rho_po = zeros(M, R);
131Cd = ones(M, R);
132% Stage 1 only seeds the Stage 2 flow iteration, so a moderate iteration
133% budget suffices; updates are damped and step-clamped to avoid limit
134% cycles when the population target sits beyond the saturation cap
135maxit1 = min(options.maxiter, 100);
136for it1 = 1:maxit1
137 iter = iter + 1;
138 X = me_cqn_capacity_cap(X, V, mu, c, M, R);
139 lambda = V .* repmat(X, M, 1);
140 [Lpo, Ca, Cd, rho_po] = me_cqn_pseudoopen(M, R, lambda, mu, mu_eff, Cs_eff, P_eff, selfp, c, insens, Ca, options);
141 Ltot = sum(Lpo, 1);
142 err1 = 0;
143 for r = 1:R
144 if N(r) > 0 && Ltot(r) > 0
145 err1 = max(err1, abs(Ltot(r) - N(r)) / N(r));
146 end
147 end
148 if options.verbose
149 fprintf('Stage 1 iteration %d: max population error = %e\n', int32(it1), err1);
150 end
151 if err1 < options.tol
152 break;
153 end
154 Xold = X;
155 for r = 1:R
156 if Ltot(r) > 0
157 fac = min(max((N(r) / Ltot(r))^0.5, 0.25), 4); % clamped step
158 X(r) = 0.5 * X(r) + 0.5 * X(r) * fac; % damped update
159 end
160 end
161 % Stall guard: the stability cap can bind before the population
162 % target is met (bottleneck saturation); stop when X no longer moves
163 if max(abs(me_cqn_capacity_cap(X, V, mu, c, M, R) - Xold) ./ max(Xold, 1e-12)) < options.tol
164 break;
165 end
166end
167
168% Stage 2: closed ME solution by convolution, iterated on the flow
169% (work rate) equations
170sz = N + 1;
171[Dec, PIdx] = me_cqn_lattice(N, R);
172L = Lpo;
173rho = rho_po;
174for it2 = 1:options.maxiter
175 iter = iter + 1;
176 % Lagrangian coefficient functions f_i over the population lattice
177 F = me_cqn_coefficients(M, R, PIdx, Dec, sz, Lpo, rho_po, lambda, mu_eff, Cs_eff, Ca, c, selfp);
178 % Convolution and per-station marginals
179 [L, U] = me_cqn_convolve(M, R, N, PIdx, Dec, sz, F, c);
180 % Utilization split by pseudo-open per-class load; implied throughputs
181 % from the work rate theorem, averaged with visit weights
182 rho = zeros(M, R);
183 Xhat = zeros(1, R);
184 for r = 1:R
185 num = 0;
186 den = 0;
187 for i = 1:M
188 if lambda(i, r) > 0
189 if isinf(c(i))
190 rho(i, r) = L(i, r);
191 % IS work rate: lambda_eff = L*mu_eff, revisits add 1/(1-p)
192 num = num + L(i, r) * mu_eff(i, r) / (1 - selfp(i, r));
193 else
194 rho_i = sum(rho_po(i, :));
195 if rho_i > 0
196 rho(i, r) = U(i) * rho_po(i, r) / rho_i;
197 end
198 num = num + rho(i, r) * mu(i, r);
199 end
200 den = den + V(i, r);
201 end
202 end
203 if den > 0
204 Xhat(r) = num / den;
205 end
206 end
207 err2 = 0;
208 for r = 1:R
209 if X(r) > 0
210 err2 = max(err2, abs(Xhat(r) - X(r)) / X(r));
211 end
212 end
213 if options.verbose
214 fprintf('Stage 2 iteration %d: max flow error = %e\n', int32(it2), err2);
215 end
216 if err2 < options.tol
217 break;
218 end
219 % Damped throughput update with stability cap, then refresh the
220 % pseudo-open decomposition at the new flows
221 Xold = X;
222 X = 0.5 * X + 0.5 * Xhat;
223 X = me_cqn_capacity_cap(X, V, mu, c, M, R);
224 % Stall guard: when the stability cap binds, X stops moving even
225 % though the residual flow error stays above tolerance
226 if max(abs(X - Xold) ./ max(Xold, 1e-12)) < options.tol
227 break;
228 end
229 lambda = V .* repmat(X, M, 1);
230 [Lpo, Ca, Cd, rho_po] = me_cqn_pseudoopen(M, R, lambda, mu, mu_eff, Cs_eff, P_eff, selfp, c, insens, Ca, options);
231end
232
233if it2 == options.maxiter && err2 >= options.tol
234 warning('me_cqn:noconverge', 'Did not converge within %d iterations (flow error=%e)', int32(options.maxiter), err2);
235end
236
237% Response times by Little's law on the visit-inclusive throughputs
238lambda = V .* repmat(X, M, 1);
239W = zeros(M, R);
240for i = 1:M
241 for r = 1:R
242 if lambda(i, r) > 0
243 W(i, r) = L(i, r) / lambda(i, r);
244 end
245 end
246end
247
248end
249
250%% ------------------------------------------------------------------------
251function X = me_cqn_capacity_cap(X, V, mu, c, M, R)
252% Scales the class throughputs uniformly so that every single-server
253% queue in the pseudo-open network remains stable
254maxrho = 0;
255for i = 1:M
256 if ~isinf(c(i))
257 rho_i = 0;
258 for r = 1:R
259 if V(i, r) > 0 && mu(i, r) > 0
260 rho_i = rho_i + X(r) * V(i, r) / mu(i, r);
261 end
262 end
263 maxrho = max(maxrho, rho_i);
264 end
265end
266if maxrho >= 0.999
267 X = X * (0.999 / maxrho);
268end
269end
270
271function [L, Ca, Cd, rho] = me_cqn_pseudoopen(M, R, lambda, mu, mu_eff, Cs_eff, P_eff, selfp, c, insens, Ca, options)
272% GE-type fixed point of the open algorithm (Section 3.2) on the
273% pseudo-open network: no external arrivals, flows given by lambda.
274% The flow scvs are computed on the class-composed (aggregate) streams
275% and disaggregated per class by thinning, following the class
276% composition and disaggregation principle of the closed ME algorithm
277% (Kouvatsos 1994, Section 3.3 discussion); this keeps the closed
278% solution consistent with the single-class one when classes are
279% statistically identical.
280lambda_eff = lambda .* (1 - selfp);
281rho = zeros(M, R);
282for i = 1:M
283 for r = 1:R
284 if mu(i, r) > 0
285 if isinf(c(i))
286 rho(i, r) = lambda_eff(i, r) / mu_eff(i, r);
287 else
288 rho(i, r) = lambda(i, r) / mu(i, r);
289 end
290 end
291 end
292end
293% Class composition per station: aggregate flow, service process moments
294% and flow-weighted aggregate routing
295lam_a = sum(lambda_eff, 2)';
296mu_a = zeros(1, M);
297Cs_a = ones(1, M);
298for i = 1:M
299 if lam_a(i) > 0
300 ES = 0;
301 ES2 = 0;
302 for u = 1:R
303 if lambda_eff(i, u) > 0 && mu_eff(i, u) > 0
304 wu = lambda_eff(i, u) / lam_a(i);
305 ES = ES + wu / mu_eff(i, u);
306 ES2 = ES2 + wu * (Cs_eff(i, u) + 1) / mu_eff(i, u)^2;
307 end
308 end
309 if ES > 0
310 mu_a(i) = 1 / ES;
311 Cs_a(i) = ES2 / ES^2 - 1;
312 end
313 end
314end
315Pa = zeros(M, M);
316for j = 1:M
317 if lam_a(j) > 0
318 for i = 1:M
319 num = 0;
320 for r = 1:R
321 if lambda_eff(j, r) > 0
322 num = num + lambda_eff(j, r) * P_eff(j, i, r);
323 end
324 end
325 Pa(j, i) = num / lam_a(j);
326 end
327 end
328end
329% Fixed point on the aggregate arrival scvs
330Ca_a = ones(1, M);
331for i = 1:M
332 if lam_a(i) > 0 && any(lambda_eff(i, :) > 0)
333 wr = find(lambda_eff(i, :) > 0, 1);
334 Ca_a(i) = 1 + (Ca(i, wr) - 1) * lam_a(i) / max(lambda_eff(i, wr), realmin); % warm start
335 end
336end
337Cd_a = ones(1, M);
338L_a = zeros(1, M);
339for it = 1:options.maxiter
340 Ca_old = Ca_a;
341 for i = 1:M
342 if lam_a(i) <= 0
343 continue;
344 end
345 rho_i = sum(rho(i, :));
346 if isinf(c(i))
347 % GE/GE/inf: L = lambda/mu, departures inherit the arrival scv
348 L_a(i) = lam_a(i) / mu_a(i);
349 Cd_a(i) = Ca_a(i);
350 elseif rho_i < 1
351 if insens(i)
352 % Insensitive disciplines (PS, LCFS-PR): product-form mql
353 L_a(i) = rho_i / (1 - rho_i);
354 else
355 % Single-class GE/GE/1 mql, eq. (3.6)
356 L_a(i) = rho_i * (Ca_a(i) + 1) / 2 + rho_i^2 * (Ca_a(i) + Cs_a(i)) / (2 * (1 - rho_i));
357 end
358 Cd_a(i) = 2 * L_a(i) * (1 - rho_i) + Ca_a(i) * (1 - 2 * rho_i);
359 end
360 end
361 % GE-type merging, eq. (3.7) with lambda_o = 0, on aggregate flows
362 for i = 1:M
363 if lam_a(i) > 0
364 sum_inv = 0;
365 for j = 1:M
366 if Pa(j, i) > 0 && lam_a(j) > 0
367 Cdji = 1 + Pa(j, i) * (Cd_a(j) - 1);
368 sum_inv = sum_inv + (lam_a(j) * Pa(j, i) / lam_a(i)) / (Cdji + 1);
369 end
370 end
371 if sum_inv > 0
372 Ca_a(i) = -1 + 1 / sum_inv;
373 end
374 end
375 end
376 delta = max(abs(Ca_a - Ca_old));
377 if delta < options.tol
378 break;
379 end
380end
381% Disaggregation: per-class arrival scvs by thinning of the composed
382% stream, then per-class mean queue lengths (Section 3.1.1)
383L = zeros(M, R);
384Cd = ones(M, R);
385for i = 1:M
386 rho_i = sum(rho(i, :));
387 for r = 1:R
388 if lambda_eff(i, r) > 0
389 pr = lambda_eff(i, r) / lam_a(i);
390 Ca(i, r) = 1 + pr * (Ca_a(i) - 1);
391 Cd(i, r) = 1 + pr * (Cd_a(i) - 1);
392 end
393 end
394 if isinf(c(i))
395 for r = 1:R
396 if lambda_eff(i, r) > 0 && mu_eff(i, r) > 0
397 L(i, r) = lambda_eff(i, r) / mu_eff(i, r);
398 end
399 end
400 elseif rho_i < 1
401 if insens(i)
402 % Insensitive disciplines (PS, LCFS-PR): product-form mql
403 for r = 1:R
404 if lambda_eff(i, r) > 0 && mu_eff(i, r) > 0
405 L(i, r) = rho(i, r) / (1 - rho_i);
406 end
407 end
408 else
409 resid = 0;
410 for u = 1:R
411 if lambda_eff(i, u) > 0 && mu_eff(i, u) > 0
412 resid = resid + lambda_eff(i, u) * (Cs_eff(i, u) + Ca(i, u)) / mu_eff(i, u)^2;
413 end
414 end
415 for r = 1:R
416 if lambda_eff(i, r) > 0 && mu_eff(i, r) > 0
417 L(i, r) = rho(i, r) * (Ca(i, r) + 1) / 2 + lambda_eff(i, r) * resid / (2 * (1 - rho_i));
418 end
419 end
420 end
421 end
422end
423end
424
425function [Dec, PIdx] = me_cqn_lattice(N, R)
426% Enumerates the population lattice {0..N(1)} x ... x {0..N(R)} in mixed
427% radix order; Dec(p,:) is the population vector of linear index p
428sz = N + 1;
429PIdx = prod(sz);
430Dec = zeros(PIdx, R);
431for p = 1:PIdx
432 q = p - 1;
433 for r = 1:R
434 Dec(p, r) = mod(q, sz(r));
435 q = floor(q / sz(r));
436 end
437end
438end
439
440function F = me_cqn_coefficients(M, R, PIdx, Dec, sz, Lpo, rho_po, lambda, mu_eff, Cs_eff, Ca, c, selfp)
441% Auxiliary functions f_i(n) of the ME solution (3.8): the right-hand
442% sides of (3.2) and (3.4) with the (1-rho) factor removed, evaluated
443% from the Stage 1 Lagrangian coefficients. Each f_i is rescaled by its
444% maximum for numerical stability (per-station constants cancel in the
445% marginal probabilities).
446F = zeros(PIdx, M);
447lambda_eff = lambda .* (1 - selfp);
448lgamma = gammaln(1:(sum(sz - 1) + 2)); % lgamma(k) = log((k-1)!)
449for i = 1:M
450 if isinf(c(i))
451 % GE/GE/inf: f(n) = prod_r prod_{k=1}^{n_r} g_r(k), with g_r(j)
452 % from the exact ME solution of the GE/GE/inf queue
453 logg = cell(1, R);
454 for r = 1:R
455 logg{r} = zeros(1, sz(r) - 1);
456 for j = 1:(sz(r) - 1)
457 if lambda_eff(i, r) > 0 && mu_eff(i, r) > 0
458 gj = (lambda_eff(i, r) * (1 + Cs_eff(i, r)) + (j - 1) * mu_eff(i, r) * (Ca(i, r) - 1)) ...
459 / (j * mu_eff(i, r) * (Ca(i, r) + Cs_eff(i, r)));
460 logg{r}(j) = log(max(gj, 0));
461 else
462 logg{r}(j) = -Inf;
463 end
464 end
465 end
466 for p = 1:PIdx
467 n = Dec(p, :);
468 val = 0;
469 for r = 1:R
470 for j = 1:n(r)
471 val = val + logg{r}(j);
472 end
473 end
474 F(p, i) = exp(val);
475 end
476 F(1, i) = 1;
477 else
478 % GE/GE/1 (non-priority): f(n) = ((|n|-1)!/prod_r n_r!)
479 % * sum_r n_r*(g_r*x_r)*x_r^(n_r-1)*prod_{s~=r} x_s^{n_s}
480 % with x_r = (L_r-rho_r)/L and g_r*x_r = rho_r*rho/((1-rho)*L)
481 rho_i = sum(rho_po(i, :));
482 Li = sum(Lpo(i, :));
483 x = zeros(1, R);
484 gx = zeros(1, R);
485 if Li > 0 && rho_i < 1
486 for r = 1:R
487 if lambda(i, r) > 0
488 x(r) = max(Lpo(i, r) - rho_po(i, r), 0) / Li;
489 gx(r) = rho_po(i, r) * rho_i / ((1 - rho_i) * Li);
490 end
491 end
492 end
493 for p = 1:PIdx
494 n = Dec(p, :);
495 ntot = sum(n);
496 if ntot == 0
497 F(p, i) = 1;
498 continue;
499 end
500 if any(n > 0 & lambda(i, :) <= 0)
501 F(p, i) = 0; % class not visiting this station
502 continue;
503 end
504 logmult = lgamma(ntot) - sum(lgamma(n + 1)); % (|n|-1)!/prod n_r!
505 tot = 0;
506 for r = 1:R
507 if n(r) > 0 && gx(r) > 0
508 % n_r * gx_r * x_r^(n_r-1) * prod_{s~=r} x_s^{n_s}
509 lterm = log(n(r)) + log(gx(r));
510 ok = true;
511 for s = 1:R
512 es = n(s);
513 if s == r
514 es = es - 1;
515 end
516 if es > 0
517 if x(s) > 0
518 lterm = lterm + es * log(x(s));
519 else
520 ok = false;
521 break;
522 end
523 end
524 end
525 if ok
526 tot = tot + exp(logmult + lterm);
527 end
528 end
529 end
530 F(p, i) = tot;
531 end
532 end
533 fmax = max(F(:, i));
534 if fmax > 0
535 F(:, i) = F(:, i) / fmax;
536 end
537 F(1, i) = max(F(1, i), realmin); % f_i(0) stays positive after scaling
538end
539end
540
541function [L, U] = me_cqn_convolve(M, R, N, PIdx, Dec, sz, F, c)
542% Computes the normalising constant by convolving the f_i over the
543% population lattice, and the per-station marginals by prefix/suffix
544% convolutions; returns closed mean queue lengths and busy probabilities
545rad = ones(1, R);
546for r = 2:R
547 rad(r) = rad(r - 1) * sz(r - 1);
548end
549% Prefix and suffix convolutions
550Gpre = cell(1, M + 1);
551Gsuf = cell(1, M + 2);
552G0 = zeros(PIdx, 1);
553G0(1) = 1;
554Gpre{1} = G0;
555for k = 1:M
556 Gpre{k + 1} = me_cqn_convpair(Gpre{k}, F(:, k), PIdx, Dec, rad);
557end
558Gsuf{M + 1} = G0;
559for k = M:-1:1
560 Gsuf{k} = me_cqn_convpair(Gsuf{k + 1}, F(:, k), PIdx, Dec, rad);
561end
562Z = Gpre{M + 1}(PIdx);
563L = zeros(M, R);
564U = zeros(M, 1);
565for i = 1:M
566 Grest = me_cqn_convpair(Gpre{i}, Gsuf{i + 1}, PIdx, Dec, rad);
567 % Marginal P_i(n) = f_i(n) * Grest(N - n) / Z
568 for p = 1:PIdx
569 if F(p, i) > 0
570 n = Dec(p, :);
571 q = 1 + (N - n) * rad';
572 pin = F(p, i) * Grest(q) / Z;
573 if p > 1
574 U(i) = U(i) + pin;
575 end
576 for r = 1:R
577 if n(r) > 0
578 L(i, r) = L(i, r) + n(r) * pin;
579 end
580 end
581 end
582 end
583end
584end
585
586function G2 = me_cqn_convpair(G, f, PIdx, Dec, rad)
587% Convolution of a partial normalising constant with one station term
588% over the population lattice
589G2 = zeros(PIdx, 1);
590for p = 1:PIdx % station population n
591 if f(p) == 0
592 continue;
593 end
594 n = Dec(p, :);
595 for q = 1:PIdx % remainder population m
596 if G(q) == 0
597 continue;
598 end
599 m = Dec(q, :);
600 t = n + m;
601 idx = 1 + t * rad';
602 if idx <= PIdx && all(t <= Dec(PIdx, :))
603 G2(idx) = G2(idx) + f(p) * G(q);
604 end
605 end
606end
607end
Definition Station.m:245