1function [QN,UN,RN,TN,CN,XN,totiter] = solver_nc_mem(sn, options)
2% [QN,UN,RN,TN,CN,XN,TOTITER] = SOLVER_NC_MEM(SN, OPTIONS)
4% Maximum Entropy Method (MEM)
for Open and Closed Queueing Networks
6% Implements
the ME algorithms from Kouvatsos (1994) for analyzing
7% queueing networks with general arrival and service processes under
8% non-priority scheduling disciplines. Open models (Section 3.2) are
9% decomposed into GE/GE/1, GE/GE/c and GE/GE/inf building blocks; closed
10% models (Section 3.3) are solved by
the two-stage pseudo-open network
11% plus convolution algorithm on G/G/1 and G/G/inf building blocks; mixed
12% models compose
the two algorithms by product-form-style conditioning
13% (open classes reduce
the server capacity seen by
the closed classes,
14% closed occupancy inflates
the open queue lengths).
17% sn - Network structure from getStruct()
18% options - Solver options with MEM-specific fields:
19% - config.mem_tol: convergence tolerance (default 1e-6)
20% - config.mem_maxiter: maximum iterations (default 1000)
21% - config.mem_verbose: print iteration info (default false)
24% QN - Mean queue lengths [M x R matrix]
25% UN - Utilizations [M x R matrix]
26% RN - Mean response times [M x R matrix]
27% TN - Throughputs [M x R matrix]
28% CN - System response times per class [1 x R], by Little's law
29% XN - System throughputs per class [1 x R]
30% totiter - Number of iterations until convergence
33% D.D. Kouvatsos, "Entropy Maximisation and Queueing Network Models",
34% Annals of Operations Research, 48:63-126, 1994.
36% Copyright (c) 2012-2026, Imperial College London
39% Validate
the model against
the MEM feature set
40[memok, memreason] = solver_nc_mem_supports(sn);
42 line_error(mfilename, memreason);
48% Get MEM options from config
49if isfield(options, 'config') && isfield(options.config, 'mem_tol')
50 tol = options.config.mem_tol;
55if isfield(options, 'config') && isfield(options.config, 'mem_maxiter')
56 maxiter = options.config.mem_maxiter;
61if isfield(options, 'config') && isfield(options.config, 'mem_verbose')
62 verbose = options.config.mem_verbose;
67% Create MEM options structure
68mem_options = struct();
70mem_options.maxiter = maxiter;
71mem_options.verbose = verbose;
73% Closed models: two-stage pseudo-open + convolution algorithm (Section 3.3)
74if sn_is_closed_model(sn)
76 refstat = zeros(1, R);
79 refstat(r) = sn.refstat(r);
83 nservers = ones(M, 1);
85 nservers(ist) = sn.nservers(ist);
87 if isfinite(sn.rates(ist, r)) && sn.rates(ist, r) > 0
88 mu(ist, r) = sn.rates(ist, r);
89 if isfinite(sn.scv(ist, r)) && sn.scv(ist, r) > 0
90 Cs(ist, r) = sn.scv(ist, r);
95 % Routing probabilities between stations
99 jNode = sn.stationToNode(j);
101 iNode = sn.stationToNode(k);
102 P(j, k, r) = sn.rtnodes((jNode-1)*R + r, (iNode-1)*R + r);
106 insens = false(M, 1);
108 insens(ist) = any(sn.sched(ist) == [SchedStrategy.PS, SchedStrategy.LCFSPR]);
110 [L, W, ~, ~, lam, rho, X, totiter] = me_cqn(M, R, N, mu, Cs,
P, nservers, refstat, insens, mem_options);
119 CN(r) = N(r) / X(r); % class cycle time by Little's law
125% Mixed models: composition of
the open (Section 3.2) and closed
126% (Section 3.3) algorithms with product-form-style conditioning
127if ~sn_is_open_model(sn)
128 openCls = false(1, R);
132 openCls(r) = isinf(sn.njobs(r));
135 for ind = 1:sn.nnodes
136 if sn.nodetype(ind) == NodeType.Source
137 sourceIdx = sn.nodeToStation(ind);
141 qs = setdiff(1:M, sourceIdx); % queueing/delay stations
145 nservers = ones(Mq, 1);
146 refstat = zeros(1, R);
149 nservers(k) = sn.nservers(ist);
151 if isfinite(sn.rates(ist, r)) && sn.rates(ist, r) > 0
152 mu(k, r) = sn.rates(ist, r);
153 if isfinite(sn.scv(ist, r)) && sn.scv(ist, r) > 0
154 Cs(k, r) = sn.scv(ist, r);
161 refstat(r) = find(qs == sn.refstat(r), 1);
164 % External arrivals of
the open classes along
the source routing
165 lambda0 = zeros(Mq, R);
167 sourceNode = sn.stationToNode(sourceIdx);
169 if openCls(r) && isfinite(sn.rates(sourceIdx, r)) && sn.rates(sourceIdx, r) > 0
170 extRate = sn.rates(sourceIdx, r);
172 if isfinite(sn.scv(sourceIdx, r)) && sn.scv(sourceIdx, r) > 0
173 Ca_ext = sn.scv(sourceIdx, r);
176 destNode = sn.stationToNode(qs(k));
177 routeProb = sn.rtnodes((sourceNode-1)*R + r, (destNode-1)*R + r);
179 lambda0(k, r) = extRate * routeProb;
185 % Routing probabilities between queueing stations
186 P = zeros(Mq, Mq, R);
189 jNode = sn.stationToNode(qs(j));
191 iNode = sn.stationToNode(qs(k));
192 P(j, k, r) = sn.rtnodes((jNode-1)*R + r, (iNode-1)*R + r);
196 insens = false(Mq, 1);
198 insens(k) = any(sn.sched(qs(k)) == [SchedStrategy.PS, SchedStrategy.LCFSPR]);
200 [L, W, ~, ~, lam, rho, X, totiter] = me_mqn(Mq, R, openCls, lambda0, Ca0, N, mu, Cs,
P, nservers, refstat, insens, mem_options);
209 % Cap utilization of unstable stations at 1 (LINE convention)
212 if isfinite(sn.nservers(ist))
213 Utot = sum(UN(ist, :));
215 UN(ist, :) = UN(ist, :) / Utot;
223 TN(sourceIdx, r) = X(r);
226 CN(r) = sum(QN(qs, r)) / X(r);
231 CN(r) = N(r) / X(r); % class cycle time
238% Locate
the source station (external arrivals)
241 if sn.nodetype(ind) == NodeType.Source
242 sourceIdx = sn.nodeToStation(ind);
247 line_error(mfilename, 'MEM requires a Source node.');
250qs = setdiff(1:M, sourceIdx); % queueing/delay stations
253% Service rates, scvs and server counts (rates/scv are NaN for classes
254% not served at a station)
257nservers = ones(Mq, 1);
260 nservers(k) = sn.nservers(ist);
262 if isfinite(sn.rates(ist, r)) && sn.rates(ist, r) > 0
263 mu(k, r) = sn.rates(ist, r);
264 if isfinite(sn.scv(ist, r)) && sn.scv(ist, r) > 0
265 Cs(k, r) = sn.scv(ist, r);
271% External arrivals: distribute
the source output along its routing
272lambda0 = zeros(Mq, R);
274sourceNode = sn.stationToNode(sourceIdx);
276 if isfinite(sn.rates(sourceIdx, r)) && sn.rates(sourceIdx, r) > 0
277 extRate = sn.rates(sourceIdx, r);
279 if isfinite(sn.scv(sourceIdx, r)) && sn.scv(sourceIdx, r) > 0
280 Ca_ext = sn.scv(sourceIdx, r);
283 destNode = sn.stationToNode(qs(k));
284 routeProb = sn.rtnodes((sourceNode-1)*R + r, (destNode-1)*R + r);
286 lambda0(k, r) = extRate * routeProb;
293% Routing probabilities between queueing stations
297 jNode = sn.stationToNode(qs(j));
299 iNode = sn.stationToNode(qs(k));
300 P(j, k, r) = sn.rtnodes((jNode-1)*R + r, (iNode-1)*R + r);
305% Run
the Maximum Entropy fixed-point algorithm
306insens = false(Mq, 1);
308 insens(k) = any(sn.sched(qs(k)) == [SchedStrategy.PS, SchedStrategy.LCFSPR]);
310[L, W, ~, ~, lam, rho, totiter] = me_oqn(Mq, R, lambda0, Ca0, mu, Cs,
P, nservers, insens, mem_options);
312% Map results back to station-indexed LINE outputs
322% Cap utilization of unstable stations at 1 (LINE convention)
325 if isfinite(sn.nservers(ist))
326 Utot = sum(UN(ist, :));
328 UN(ist, :) = UN(ist, :) / Utot;
333% Source station: report
the external arrival rates as throughputs and
334% derive system metrics by Little's law
338 if isfinite(sn.rates(sourceIdx, r)) && sn.rates(sourceIdx, r) > 0
339 TN(sourceIdx, r) = sn.rates(sourceIdx, r);
340 XN(r) = sn.rates(sourceIdx, r);
341 CN(r) = sum(QN(qs, r)) / XN(r);