1function [QN,UN,RN,TN,CN,XN,totiter,actualmethod] = solver_nc_mem(sn, options)
2% [QN,UN,RN,TN,CN,XN,TOTITER,ACTUALMETHOD] = 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
31% actualmethod - 'mem', or 'mem.blocking' when the model carries a finite
32% station buffer and was solved by the censored GE/GE/c/0;N
33% building blocks of Section 4.1
36% D.D. Kouvatsos, "Entropy Maximisation and Queueing Network Models",
37% Annals of Operations Research, 48:63-126, 1994.
39% Copyright (c) 2012-2026, Imperial College London
44% Validate the model against the MEM feature set
45[memok, memreason, memblocking] = solver_nc_mem_supports(sn);
47 line_error(mfilename, memreason);
53% Get MEM options from config
54if isfield(options, 'config') && isfield(options.config, 'mem_tol')
55 tol = options.config.mem_tol;
60if isfield(options, 'config') && isfield(options.config, 'mem_maxiter')
61 maxiter = options.config.mem_maxiter;
66if isfield(options, 'config') && isfield(options.config, 'mem_verbose')
67 verbose = options.config.mem_verbose;
72% Create MEM options structure
73mem_options = struct();
75mem_options.maxiter = maxiter;
76mem_options.verbose = verbose;
78% Closed models: two-stage pseudo-open + convolution algorithm (Section 3.3)
79if sn_is_closed_model(sn)
81 refstat = zeros(1, R);
84 refstat(r) = sn.refstat(r);
88 nservers = ones(M, 1);
90 nservers(ist) = sn.nservers(ist);
92 if isfinite(sn.rates(ist, r)) && sn.rates(ist, r) > 0
93 mu(ist, r) = sn.rates(ist, r);
94 if isfinite(sn.scv(ist, r)) && sn.scv(ist, r) > 0
95 Cs(ist, r) = sn.scv(ist, r);
100 % Routing probabilities between stations
104 jNode = sn.stationToNode(j);
106 iNode = sn.stationToNode(k);
107 P(j, k, r) = sn.rtnodes((jNode-1)*R + r, (iNode-1)*R + r);
111 insens = false(M, 1);
113 insens(ist) = any(sn.sched(ist) == [SchedStrategy.PS, SchedStrategy.LCFSPR]);
115 [L, W, ~, ~, lam, rho, X, totiter] = me_cqn(M, R, N, mu, Cs,
P, nservers, refstat, insens, mem_options);
124 CN(r) = N(r) / X(r); % class cycle time by Little's law
130% Mixed models: composition of the open (Section 3.2) and closed
131% (Section 3.3) algorithms with product-
form-style conditioning
132if ~sn_is_open_model(sn)
133 openCls = false(1, R);
137 openCls(r) = isinf(sn.njobs(r));
140 for ind = 1:sn.nnodes
141 if sn.nodetype(ind) == NodeType.Source
142 sourceIdx = sn.nodeToStation(ind);
146 qs = setdiff(1:M, sourceIdx); % queueing/delay stations
150 nservers = ones(Mq, 1);
151 refstat = zeros(1, R);
154 nservers(k) = sn.nservers(ist);
156 if isfinite(sn.rates(ist, r)) && sn.rates(ist, r) > 0
157 mu(k, r) = sn.rates(ist, r);
158 if isfinite(sn.scv(ist, r)) && sn.scv(ist, r) > 0
159 Cs(k, r) = sn.scv(ist, r);
166 refstat(r) = find(qs == sn.refstat(r), 1);
169 % External arrivals of the open classes along the source routing
170 lambda0 = zeros(Mq, R);
172 sourceNode = sn.stationToNode(sourceIdx);
174 if openCls(r) && isfinite(sn.rates(sourceIdx, r)) && sn.rates(sourceIdx, r) > 0
175 extRate = sn.rates(sourceIdx, r);
177 if isfinite(sn.scv(sourceIdx, r)) && sn.scv(sourceIdx, r) > 0
178 Ca_ext = sn.scv(sourceIdx, r);
181 destNode = sn.stationToNode(qs(k));
182 routeProb = sn.rtnodes((sourceNode-1)*R + r, (destNode-1)*R + r);
184 lambda0(k, r) = extRate * routeProb;
190 % Routing probabilities between queueing stations
191 P = zeros(Mq, Mq, R);
194 jNode = sn.stationToNode(qs(j));
196 iNode = sn.stationToNode(qs(k));
197 P(j, k, r) = sn.rtnodes((jNode-1)*R + r, (iNode-1)*R + r);
201 insens = false(Mq, 1);
203 insens(k) = any(sn.sched(qs(k)) == [SchedStrategy.PS, SchedStrategy.LCFSPR]);
205 [L, W, ~, ~, lam, rho, X, totiter] = me_mqn(Mq, R, openCls, lambda0, Ca0, N, mu, Cs,
P, nservers, refstat, insens, mem_options);
214 % Cap utilization of unstable stations at 1 (LINE convention)
217 if isfinite(sn.nservers(ist))
218 Utot = sum(UN(ist, :));
220 UN(ist, :) = UN(ist, :) / Utot;
228 TN(sourceIdx, r) = X(r);
231 CN(r) = sum(QN(qs, r)) / X(r);
236 CN(r) = N(r) / X(r); % class cycle time
243% Locate the source station (external arrivals)
246 if sn.nodetype(ind) == NodeType.Source
247 sourceIdx = sn.nodeToStation(ind);
252 line_error(mfilename, 'MEM requires a Source node.');
255qs = setdiff(1:M, sourceIdx); % queueing/delay stations
258% Service rates, scvs and server counts (rates/scv are NaN for classes
259% not served at a station)
262nservers = ones(Mq, 1);
265 nservers(k) = sn.nservers(ist);
267 if isfinite(sn.rates(ist, r)) && sn.rates(ist, r) > 0
268 mu(k, r) = sn.rates(ist, r);
269 if isfinite(sn.scv(ist, r)) && sn.scv(ist, r) > 0
270 Cs(k, r) = sn.scv(ist, r);
276% External arrivals: distribute the source output along its routing
277lambda0 = zeros(Mq, R);
279sourceNode = sn.stationToNode(sourceIdx);
281 if isfinite(sn.rates(sourceIdx, r)) && sn.rates(sourceIdx, r) > 0
282 extRate = sn.rates(sourceIdx, r);
284 if isfinite(sn.scv(sourceIdx, r)) && sn.scv(sourceIdx, r) > 0
285 Ca_ext = sn.scv(sourceIdx, r);
288 destNode = sn.stationToNode(qs(k));
289 routeProb = sn.rtnodes((sourceNode-1)*R + r, (destNode-1)*R + r);
291 lambda0(k, r) = extRate * routeProb;
298% Routing probabilities between queueing stations
302 jNode = sn.stationToNode(qs(j));
304 iNode = sn.stationToNode(qs(k));
305 P(j, k, r) = sn.rtnodes((jNode-1)*R + r, (iNode-1)*R + r);
310% Finite buffers: censored GE/GE/c/0;N building blocks, with the
311% holding-node expansion where the drop rule
is BAS (Section 4.1 of the
312% source plus Tahilramani, Manjunath and Bose 1999)
314 actualmethod = 'mem.blocking';
316 blockrule = zeros(Mq, 1);
319 Nbuf(k) = sn_get_buffer_size(sn, ist);
320 dr = DropStrategy.DROP;
321 if ~isempty(sn.droprule) && size(sn.droprule, 1) >= ist
322 dr = sn.droprule(ist, 1);
324 if dr == DropStrategy.BAS
328 [L, W, Tk, rho, ~, ~, PBa, ~, totiter] = me_oqn_blk(Mq, lambda0(:, 1), Ca0(:, 1), ...
329 mu(:, 1), Cs(:, 1),
P(:, :, 1), nservers, Nbuf, blockrule, mem_options);
338 % The source emits at its nominal rate; the jobs lost at a full buffer
339 % never reach a station, so the carried flow reported per station
is
340 % below it by the loss probability at the entry stations.
343 if isfinite(sn.rates(sourceIdx, 1)) && sn.rates(sourceIdx, 1) > 0
344 TN(sourceIdx, 1) = sn.rates(sourceIdx, 1);
345 XN(1) = sn.rates(sourceIdx, 1);
349 accepted = accepted - lambda0(k, 1) * PBa(k) * (blockrule(k) == 0);
353 CN(1) = sum(QN(qs, 1)) / accepted;
359% Run the Maximum Entropy fixed-point algorithm
360insens = false(Mq, 1);
362 insens(k) = any(sn.sched(qs(k)) == [SchedStrategy.PS, SchedStrategy.LCFSPR]);
364[L, W, ~, ~, lam, rho, totiter] = me_oqn(Mq, R, lambda0, Ca0, mu, Cs,
P, nservers, insens, mem_options);
366% Map results back to station-indexed LINE outputs
376% Cap utilization of unstable stations at 1 (LINE convention)
379 if isfinite(sn.nservers(ist))
380 Utot = sum(UN(ist, :));
382 UN(ist, :) = UN(ist, :) / Utot;
387% Source station: report the external arrival rates as throughputs and
388% derive system metrics by Little's law
392 if isfinite(sn.rates(sourceIdx, r)) && sn.rates(sourceIdx, r) > 0
393 TN(sourceIdx, r) = sn.rates(sourceIdx, r);
394 XN(r) = sn.rates(sourceIdx, r);
395 CN(r) = sum(QN(qs, r)) / XN(r);