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
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.
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.
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
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)
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
46% D.D. Kouvatsos, "Entropy Maximisation and Queueing Network Models",
47% Annals of Operations Research, 48:63-126, 1994. Section 3.3.
49if nargin < 7 || isempty(c)
52if nargin < 8 || isempty(refstat)
53 refstat = zeros(1, R);
55if nargin < 9 || isempty(insens)
61insens = logical(insens(:));
62if ~isfield(options, 'tol')
65if ~isfield(options, 'maxiter')
66 options.maxiter = 1000;
68if ~isfield(options, 'verbose')
69 options.verbose = false;
74% Feedback correction (as in
the open algorithm)
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);
92% Visit ratios from
the original routing (visit-inclusive), normalised at
93%
the reference station of each class
96 A = eye(M) -
P(:, :, r)';
98 ref = find(mu(:, r) > 0, 1);
107 V(abs(V(:, r)) < 1e-14, r) = 0;
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
117 if ~isinf(c(i)) && V(i, r) > 0 && mu(i, r) > 0
118 capr = min(capr, mu(i, r) / V(i, r));
121 if isinf(capr) % IS-only class
124 X(r) = 0.5 * capr / 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);
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);
144 if N(r) > 0 && Ltot(r) > 0
145 err1 = max(err1, abs(Ltot(r) - N(r)) / N(r));
149 fprintf('Stage 1 iteration %d: max population error = %e\n', int32(it1), err1);
151 if err1 < options.tol
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
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
168% Stage 2: closed ME solution by convolution, iterated on
the flow
169% (work rate) equations
171[Dec, PIdx] = me_cqn_lattice(N, R);
174for it2 = 1:options.maxiter
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
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));
194 rho_i = sum(rho_po(i, :));
196 rho(i, r) = U(i) * rho_po(i, r) / rho_i;
198 num = num + rho(i, r) * mu(i, r);
210 err2 = max(err2, abs(Xhat(r) - X(r)) / X(r));
214 fprintf('Stage 2 iteration %d: max flow error = %e\n', int32(it2), err2);
216 if err2 < options.tol
219 % Damped throughput update with stability cap, then refresh
the
220 % pseudo-open decomposition at
the new flows
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
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);
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);
237% Response times by Little's law on
the visit-inclusive throughputs
238lambda = V .* repmat(X, M, 1);
243 W(i, r) = L(i, r) / lambda(i, r);
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
259 if V(i, r) > 0 && mu(i, r) > 0
260 rho_i = rho_i + X(r) * V(i, r) / mu(i, r);
263 maxrho = max(maxrho, rho_i);
267 X = X * (0.999 / maxrho);
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);
286 rho(i, r) = lambda_eff(i, r) / mu_eff(i, r);
288 rho(i, r) = lambda(i, r) / mu(i, r);
293% Class composition per station: aggregate flow, service process moments
294% and flow-weighted aggregate routing
295lam_a = sum(lambda_eff, 2)';
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;
311 Cs_a(i) = ES2 / ES^2 - 1;
321 if lambda_eff(j, r) > 0
322 num = num + lambda_eff(j, r) * P_eff(j, i, r);
325 Pa(j, i) = num / lam_a(j);
329% Fixed point on
the aggregate arrival scvs
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
339for it = 1:options.maxiter
345 rho_i = sum(rho(i, :));
347 % GE/GE/inf: L = lambda/mu, departures inherit
the arrival scv
348 L_a(i) = lam_a(i) / mu_a(i);
352 % Insensitive disciplines (PS, LCFS-PR): product-form mql
353 L_a(i) = rho_i / (1 - rho_i);
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));
358 Cd_a(i) = 2 * L_a(i) * (1 - rho_i) + Ca_a(i) * (1 - 2 * rho_i);
361 % GE-type merging, eq. (3.7) with lambda_o = 0, on aggregate flows
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);
372 Ca_a(i) = -1 + 1 / sum_inv;
376 delta = max(abs(Ca_a - Ca_old));
377 if delta < options.tol
381% Disaggregation: per-class arrival scvs by thinning of
the composed
382% stream, then per-class mean queue lengths (Section 3.1.1)
386 rho_i = sum(rho(i, :));
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);
396 if lambda_eff(i, r) > 0 && mu_eff(i, r) > 0
397 L(i, r) = lambda_eff(i, r) / mu_eff(i, r);
402 % Insensitive disciplines (PS, LCFS-PR): product-form mql
404 if lambda_eff(i, r) > 0 && mu_eff(i, r) > 0
405 L(i, r) = rho(i, r) / (1 - rho_i);
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;
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));
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
434 Dec(p, r) = mod(q, sz(r));
435 q = floor(q / sz(r));
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).
447lambda_eff = lambda .* (1 - selfp);
448lgamma = gammaln(1:(sum(sz - 1) + 2)); % lgamma(k) = log((k-1)!)
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
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));
471 val = val + logg{r}(j);
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, :));
485 if Li > 0 && rho_i < 1
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);
500 if any(n > 0 & lambda(i, :) <= 0)
501 F(p, i) = 0; %
class not visiting this station
504 logmult = lgamma(ntot) - sum(lgamma(n + 1)); % (|n|-1)!/prod n_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));
518 lterm = lterm + es * log(x(s));
526 tot = tot + exp(logmult + lterm);
535 F(:, i) = F(:, i) / fmax;
537 F(1, i) = max(F(1, i), realmin); % f_i(0) stays positive after scaling
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
547 rad(r) = rad(r - 1) * sz(r - 1);
549% Prefix and suffix convolutions
550Gpre = cell(1, M + 1);
551Gsuf = cell(1, M + 2);
556 Gpre{k + 1} = me_cqn_convpair(Gpre{k}, F(:, k), PIdx, Dec, rad);
560 Gsuf{k} = me_cqn_convpair(Gsuf{k + 1}, F(:, k), PIdx, Dec, rad);
562Z = Gpre{M + 1}(PIdx);
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
571 q = 1 + (N - n) * rad
';
572 pin = F(p, i) * Grest(q) / Z;
578 L(i, r) = L(i, r) + n(r) * pin;
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
590for p = 1:PIdx % station population n
595 for q = 1:PIdx % remainder population m
602 if idx <= PIdx && all(t <= Dec(PIdx, :))
603 G2(idx) = G2(idx) + f(p) * G(q);