LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
solver_nc_mem.m
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)
3%
4% Maximum Entropy Method (MEM) for Open and Closed Queueing Networks
5%
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).
15%
16% Parameters:
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)
22%
23% Returns:
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
34%
35% Reference:
36% D.D. Kouvatsos, "Entropy Maximisation and Queueing Network Models",
37% Annals of Operations Research, 48:63-126, 1994.
38%
39% Copyright (c) 2012-2026, Imperial College London
40% All rights reserved.
41
42actualmethod = 'mem';
43
44% Validate the model against the MEM feature set
45[memok, memreason, memblocking] = solver_nc_mem_supports(sn);
46if ~memok
47 line_error(mfilename, memreason);
48end
49
50M = sn.nstations;
51R = sn.nclasses;
52
53% Get MEM options from config
54if isfield(options, 'config') && isfield(options.config, 'mem_tol')
55 tol = options.config.mem_tol;
56else
57 tol = 1e-6;
58end
59
60if isfield(options, 'config') && isfield(options.config, 'mem_maxiter')
61 maxiter = options.config.mem_maxiter;
62else
63 maxiter = 1000;
64end
65
66if isfield(options, 'config') && isfield(options.config, 'mem_verbose')
67 verbose = options.config.mem_verbose;
68else
69 verbose = false;
70end
71
72% Create MEM options structure
73mem_options = struct();
74mem_options.tol = tol;
75mem_options.maxiter = maxiter;
76mem_options.verbose = verbose;
77
78% Closed models: two-stage pseudo-open + convolution algorithm (Section 3.3)
79if sn_is_closed_model(sn)
80 N = zeros(1, R);
81 refstat = zeros(1, R);
82 for r = 1:R
83 N(r) = sn.njobs(r);
84 refstat(r) = sn.refstat(r);
85 end
86 mu = zeros(M, R);
87 Cs = ones(M, R);
88 nservers = ones(M, 1);
89 for ist = 1:M
90 nservers(ist) = sn.nservers(ist);
91 for r = 1:R
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);
96 end
97 end
98 end
99 end
100 % Routing probabilities between stations
101 P = zeros(M, M, R);
102 for r = 1:R
103 for j = 1:M
104 jNode = sn.stationToNode(j);
105 for k = 1:M
106 iNode = sn.stationToNode(k);
107 P(j, k, r) = sn.rtnodes((jNode-1)*R + r, (iNode-1)*R + r);
108 end
109 end
110 end
111 insens = false(M, 1);
112 for ist = 1:M
113 insens(ist) = any(sn.sched(ist) == [SchedStrategy.PS, SchedStrategy.LCFSPR]);
114 end
115 [L, W, ~, ~, lam, rho, X, totiter] = me_cqn(M, R, N, mu, Cs, P, nservers, refstat, insens, mem_options);
116 QN = L;
117 UN = rho;
118 RN = W;
119 TN = lam;
120 XN = X;
121 CN = zeros(1, R);
122 for r = 1:R
123 if X(r) > 0
124 CN(r) = N(r) / X(r); % class cycle time by Little's law
125 end
126 end
127 return
128end
129
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);
134 N = zeros(1, R);
135 for r = 1:R
136 N(r) = sn.njobs(r);
137 openCls(r) = isinf(sn.njobs(r));
138 end
139 sourceIdx = 0;
140 for ind = 1:sn.nnodes
141 if sn.nodetype(ind) == NodeType.Source
142 sourceIdx = sn.nodeToStation(ind);
143 break;
144 end
145 end
146 qs = setdiff(1:M, sourceIdx); % queueing/delay stations
147 Mq = length(qs);
148 mu = zeros(Mq, R);
149 Cs = ones(Mq, R);
150 nservers = ones(Mq, 1);
151 refstat = zeros(1, R);
152 for k = 1:Mq
153 ist = qs(k);
154 nservers(k) = sn.nservers(ist);
155 for r = 1:R
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);
160 end
161 end
162 end
163 end
164 for r = 1:R
165 if ~openCls(r)
166 refstat(r) = find(qs == sn.refstat(r), 1);
167 end
168 end
169 % External arrivals of the open classes along the source routing
170 lambda0 = zeros(Mq, R);
171 Ca0 = zeros(Mq, R);
172 sourceNode = sn.stationToNode(sourceIdx);
173 for r = 1:R
174 if openCls(r) && isfinite(sn.rates(sourceIdx, r)) && sn.rates(sourceIdx, r) > 0
175 extRate = sn.rates(sourceIdx, r);
176 Ca_ext = 1.0;
177 if isfinite(sn.scv(sourceIdx, r)) && sn.scv(sourceIdx, r) > 0
178 Ca_ext = sn.scv(sourceIdx, r);
179 end
180 for k = 1:Mq
181 destNode = sn.stationToNode(qs(k));
182 routeProb = sn.rtnodes((sourceNode-1)*R + r, (destNode-1)*R + r);
183 if routeProb > 0
184 lambda0(k, r) = extRate * routeProb;
185 Ca0(k, r) = Ca_ext;
186 end
187 end
188 end
189 end
190 % Routing probabilities between queueing stations
191 P = zeros(Mq, Mq, R);
192 for r = 1:R
193 for j = 1:Mq
194 jNode = sn.stationToNode(qs(j));
195 for k = 1:Mq
196 iNode = sn.stationToNode(qs(k));
197 P(j, k, r) = sn.rtnodes((jNode-1)*R + r, (iNode-1)*R + r);
198 end
199 end
200 end
201 insens = false(Mq, 1);
202 for k = 1:Mq
203 insens(k) = any(sn.sched(qs(k)) == [SchedStrategy.PS, SchedStrategy.LCFSPR]);
204 end
205 [L, W, ~, ~, lam, rho, X, totiter] = me_mqn(Mq, R, openCls, lambda0, Ca0, N, mu, Cs, P, nservers, refstat, insens, mem_options);
206 QN = zeros(M, R);
207 UN = zeros(M, R);
208 RN = zeros(M, R);
209 TN = zeros(M, R);
210 QN(qs, :) = L;
211 UN(qs, :) = rho;
212 RN(qs, :) = W;
213 TN(qs, :) = lam;
214 % Cap utilization of unstable stations at 1 (LINE convention)
215 for k = 1:Mq
216 ist = qs(k);
217 if isfinite(sn.nservers(ist))
218 Utot = sum(UN(ist, :));
219 if Utot > 1
220 UN(ist, :) = UN(ist, :) / Utot;
221 end
222 end
223 end
224 XN = zeros(1, R);
225 CN = zeros(1, R);
226 for r = 1:R
227 if openCls(r)
228 TN(sourceIdx, r) = X(r);
229 XN(r) = X(r);
230 if X(r) > 0
231 CN(r) = sum(QN(qs, r)) / X(r);
232 end
233 else
234 XN(r) = X(r);
235 if X(r) > 0
236 CN(r) = N(r) / X(r); % class cycle time
237 end
238 end
239 end
240 return
241end
242
243% Locate the source station (external arrivals)
244sourceIdx = 0;
245for ind = 1:sn.nnodes
246 if sn.nodetype(ind) == NodeType.Source
247 sourceIdx = sn.nodeToStation(ind);
248 break;
249 end
250end
251if sourceIdx <= 0
252 line_error(mfilename, 'MEM requires a Source node.');
253end
254
255qs = setdiff(1:M, sourceIdx); % queueing/delay stations
256Mq = length(qs);
257
258% Service rates, scvs and server counts (rates/scv are NaN for classes
259% not served at a station)
260mu = zeros(Mq, R);
261Cs = ones(Mq, R);
262nservers = ones(Mq, 1);
263for k = 1:Mq
264 ist = qs(k);
265 nservers(k) = sn.nservers(ist);
266 for r = 1:R
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);
271 end
272 end
273 end
274end
275
276% External arrivals: distribute the source output along its routing
277lambda0 = zeros(Mq, R);
278Ca0 = zeros(Mq, R);
279sourceNode = sn.stationToNode(sourceIdx);
280for r = 1:R
281 if isfinite(sn.rates(sourceIdx, r)) && sn.rates(sourceIdx, r) > 0
282 extRate = sn.rates(sourceIdx, r);
283 Ca_ext = 1.0;
284 if isfinite(sn.scv(sourceIdx, r)) && sn.scv(sourceIdx, r) > 0
285 Ca_ext = sn.scv(sourceIdx, r);
286 end
287 for k = 1:Mq
288 destNode = sn.stationToNode(qs(k));
289 routeProb = sn.rtnodes((sourceNode-1)*R + r, (destNode-1)*R + r);
290 if routeProb > 0
291 lambda0(k, r) = extRate * routeProb;
292 Ca0(k, r) = Ca_ext;
293 end
294 end
295 end
296end
297
298% Routing probabilities between queueing stations
299P = zeros(Mq, Mq, R);
300for r = 1:R
301 for j = 1:Mq
302 jNode = sn.stationToNode(qs(j));
303 for k = 1:Mq
304 iNode = sn.stationToNode(qs(k));
305 P(j, k, r) = sn.rtnodes((jNode-1)*R + r, (iNode-1)*R + r);
306 end
307 end
308end
309
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)
313if memblocking
314 actualmethod = 'mem.blocking';
315 Nbuf = zeros(Mq, 1);
316 blockrule = zeros(Mq, 1);
317 for k = 1:Mq
318 ist = qs(k);
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);
323 end
324 if dr == DropStrategy.BAS
325 blockrule(k) = 1;
326 end
327 end
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);
330 QN = zeros(M, R);
331 UN = zeros(M, R);
332 RN = zeros(M, R);
333 TN = zeros(M, R);
334 QN(qs, 1) = L;
335 UN(qs, 1) = rho;
336 RN(qs, 1) = W;
337 TN(qs, 1) = Tk;
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.
341 XN = zeros(1, R);
342 CN = zeros(1, R);
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);
346 accepted = XN(1);
347 for k = 1:Mq
348 if lambda0(k, 1) > 0
349 accepted = accepted - lambda0(k, 1) * PBa(k) * (blockrule(k) == 0);
350 end
351 end
352 if accepted > 0
353 CN(1) = sum(QN(qs, 1)) / accepted;
354 end
355 end
356 return
357end
358
359% Run the Maximum Entropy fixed-point algorithm
360insens = false(Mq, 1);
361for k = 1:Mq
362 insens(k) = any(sn.sched(qs(k)) == [SchedStrategy.PS, SchedStrategy.LCFSPR]);
363end
364[L, W, ~, ~, lam, rho, totiter] = me_oqn(Mq, R, lambda0, Ca0, mu, Cs, P, nservers, insens, mem_options);
365
366% Map results back to station-indexed LINE outputs
367QN = zeros(M, R);
368UN = zeros(M, R);
369RN = zeros(M, R);
370TN = zeros(M, R);
371QN(qs, :) = L;
372UN(qs, :) = rho;
373RN(qs, :) = W;
374TN(qs, :) = lam;
375
376% Cap utilization of unstable stations at 1 (LINE convention)
377for k = 1:Mq
378 ist = qs(k);
379 if isfinite(sn.nservers(ist))
380 Utot = sum(UN(ist, :));
381 if Utot > 1
382 UN(ist, :) = UN(ist, :) / Utot;
383 end
384 end
385end
386
387% Source station: report the external arrival rates as throughputs and
388% derive system metrics by Little's law
389XN = zeros(1, R);
390CN = zeros(1, R);
391for r = 1:R
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);
396 end
397end
398
399end