LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
mapqn_bnd_lr.m
1function [result, x, fval, exitflag] = mapqn_bnd_lr(params, objective_queue, objective_phase, sense)
2% MAPQN_BND_LR - General Linear Reduction Bounds for MAP Queueing Networks
3%
4% MATLAB port of the Python file bnd_lr.py
5%
6% Usage:
7% [result, x, fval, exitflag] = mapqn_bnd_lr(params)
8% [result, x, fval, exitflag] = mapqn_bnd_lr(params, objective_queue)
9% [result, x, fval, exitflag] = mapqn_bnd_lr(params, objective_queue, objective_phase)
10% [result, x, fval, exitflag] = mapqn_bnd_lr(params, objective_queue, objective_phase, sense)
11%
12% Inputs:
13% params - Structure with model parameters:
14% .M - Number of queues
15% .N - Total population
16% .K - [M x 1] Number of phases for each queue
17% .mu - {M x 1} cell, each mu{i} is K(i) x K(i) completion rates
18% .v - {M x 1} cell, each v{i} is K(i) x K(i) background rates
19% .r - [M x M] Routing probabilities
20% .verbose - (optional) boolean, default true
21%
22% objective_queue - (optional) Queue index to optimize (1-based), default 1
23% objective_phase - (optional) Phase index to optimize (1-based), default 1
24% sense - (optional) 'min' or 'max', default 'max'
25%
26% Outputs:
27% result - Structure with results:
28% .objective - Objective function value
29% .exitflag - Solver exit flag
30% .U - [M x max(K)] Utilization matrix
31% .IT - [M x max(K)] Idle time matrix
32% .Q - [M x max(K)] Queue length matrix
33% x - Raw solution vector
34% fval - Objective function value
35% exitflag - Solver exit flag
36
37 if nargin < 2 || isempty(objective_queue)
38 objective_queue = 1;
39 end
40 if nargin < 3 || isempty(objective_phase)
41 objective_phase = 1;
42 end
43 if nargin < 4 || isempty(sense)
44 sense = 'max';
45 end
46
47 % Extract parameters
48 M = params.M;
49 N = params.N;
50 K = params.K(:);
51 mu = params.mu;
52 v = params.v;
53 r = params.r;
54 if isfield(params, 'verbose')
55 verbose = params.verbose;
56 else
57 verbose = true;
58 end
59
60 maxK = max(K);
61
62 % Compute transition rates q{i,j}(k,h)
63 q = cell(M, M);
64 for i = 1:M
65 for j = 1:M
66 q{i,j} = zeros(K(i), K(i));
67 for ki = 1:K(i)
68 for hi = 1:K(i)
69 if j ~= i
70 q{i,j}(ki, hi) = r(i,j) * mu{i}(ki, hi);
71 else
72 q{i,j}(ki, hi) = v{i}(ki, hi) + r(i,i) * mu{i}(ki, hi);
73 end
74 end
75 end
76 end
77 end
78
79 %% Build variable indexing
80 if verbose; fprintf('Building variable index map...\n'); end
81 varCount = 0;
82
83 % U(i,k) variables: utilization at queue i, phase k
84 Uidx = zeros(M, maxK);
85 for i = 1:M
86 for k = 1:K(i)
87 varCount = varCount + 1;
88 Uidx(i, k) = varCount;
89 end
90 end
91
92 % IT(i,k) variables: idle time at queue i, phase k
93 ITidx = zeros(M, maxK);
94 for i = 1:M
95 for k = 1:K(i)
96 varCount = varCount + 1;
97 ITidx(i, k) = varCount;
98 end
99 end
100
101 % Q(i,k) variables: mean queue length at queue i, phase k
102 Qidx = zeros(M, maxK);
103 for i = 1:M
104 for k = 1:K(i)
105 varCount = varCount + 1;
106 Qidx(i, k) = varCount;
107 end
108 end
109
110 % UP(j,kj,i,hi) variables: utilization products
111 UPidx = zeros(M, maxK, M, maxK);
112 for j = 1:M
113 for kj = 1:K(j)
114 for i = 1:M
115 for hi = 1:K(i)
116 varCount = varCount + 1;
117 UPidx(j, kj, i, hi) = varCount;
118 end
119 end
120 end
121 end
122
123 % QP(j,kj,i,hi) variables: queue-length products
124 QPidx = zeros(M, maxK, M, maxK);
125 for j = 1:M
126 for kj = 1:K(j)
127 for i = 1:M
128 for hi = 1:K(i)
129 varCount = varCount + 1;
130 QPidx(j, kj, i, hi) = varCount;
131 end
132 end
133 end
134 end
135
136 % C(j,kj,i) variables: conditional queue lengths
137 Cidx = zeros(M, maxK, M);
138 for j = 1:M
139 for kj = 1:K(j)
140 for i = 1:M
141 varCount = varCount + 1;
142 Cidx(j, kj, i) = varCount;
143 end
144 end
145 end
146
147 % I_var(j,kj,i) variables: conditional idle lengths
148 Iidx = zeros(M, maxK, M);
149 for j = 1:M
150 for kj = 1:K(j)
151 for i = 1:M
152 varCount = varCount + 1;
153 Iidx(j, kj, i) = varCount;
154 end
155 end
156 end
157
158 % p1(j,kj,i,ni,hi) variables: marginal probabilities, ni from 0 to N
159 % Stored as p1idx(j, kj, i, ni+1, hi)
160 p1idx = zeros(M, maxK, M, N+1, maxK);
161 for j = 1:M
162 for kj = 1:K(j)
163 for i = 1:M
164 for ni = 0:N
165 for hi = 1:K(i)
166 varCount = varCount + 1;
167 p1idx(j, kj, i, ni+1, hi) = varCount;
168 end
169 end
170 end
171 end
172 end
173
174 % p1c(j,kj,i,ni,hi) variables: complementary marginal probabilities, ni from 0 to N
175 % Stored as p1cidx(j, kj, i, ni+1, hi)
176 p1cidx = zeros(M, maxK, M, N+1, maxK);
177 for j = 1:M
178 for kj = 1:K(j)
179 for i = 1:M
180 for ni = 0:N
181 for hi = 1:K(i)
182 varCount = varCount + 1;
183 p1cidx(j, kj, i, ni+1, hi) = varCount;
184 end
185 end
186 end
187 end
188 end
189
190 nVars = varCount;
191 if verbose; fprintf('Total variables: %d\n', nVars); end
192
193 %% Initialize bounds
194 lb = zeros(nVars, 1);
195 ub = inf(nVars, 1);
196
197 % Set upper bounds for each variable type
198 for i = 1:M
199 for k = 1:K(i)
200 ub(Uidx(i, k)) = 1;
201 ub(ITidx(i, k)) = 1;
202 ub(Qidx(i, k)) = N;
203 end
204 end
205 for j = 1:M
206 for kj = 1:K(j)
207 for i = 1:M
208 for hi = 1:K(i)
209 ub(UPidx(j, kj, i, hi)) = 1;
210 ub(QPidx(j, kj, i, hi)) = N;
211 end
212 end
213 end
214 end
215 for j = 1:M
216 for kj = 1:K(j)
217 for i = 1:M
218 ub(Cidx(j, kj, i)) = N;
219 ub(Iidx(j, kj, i)) = N;
220 end
221 end
222 end
223 for j = 1:M
224 for kj = 1:K(j)
225 for i = 1:M
226 for ni = 0:N
227 for hi = 1:K(i)
228 ub(p1idx(j, kj, i, ni+1, hi)) = 1;
229 ub(p1cidx(j, kj, i, ni+1, hi)) = 1;
230 end
231 end
232 end
233 end
234 end
235
236 %% Build constraints
237 Aeq = [];
238 beq = [];
239 Aineq = [];
240 bineq = [];
241
242 if verbose; fprintf('Building constraints...\n'); end
243
244 %% ZER1: p1(j,k,j,0,k) = 0 for all j,k
245 % Implemented via upper bounds
246 if verbose; fprintf(' ZER1 constraints...\n'); end
247 for j = 1:M
248 for k = 1:K(j)
249 idx = p1idx(j, k, j, 0+1, k);
250 ub(idx) = 0;
251 end
252 end
253
254 %% ZER2: p1(j,k,j,nj,h) = 0 for h ~= k, all j,k,nj
255 if verbose; fprintf(' ZER2 constraints...\n'); end
256 for j = 1:M
257 for k = 1:K(j)
258 for nj = 0:N
259 for h = 1:K(j)
260 if h ~= k
261 idx = p1idx(j, k, j, nj+1, h);
262 ub(idx) = 0;
263 end
264 end
265 end
266 end
267 end
268
269 %% ZER3: p1(j,k,i,N,h) = 0 for j ~= i, all j,k,i,h
270 if verbose; fprintf(' ZER3 constraints...\n'); end
271 for j = 1:M
272 for k = 1:K(j)
273 for i = 1:M
274 if j ~= i
275 for h = 1:K(i)
276 idx = p1idx(j, k, i, N+1, h);
277 ub(idx) = 0;
278 end
279 end
280 end
281 end
282 end
283
284 %% ZER4: p1c(j,k,j,nj,h) = 0 for nj >= 1, all j,k,nj,h
285 if verbose; fprintf(' ZER4 constraints...\n'); end
286 for j = 1:M
287 for k = 1:K(j)
288 for nj = 1:N
289 for h = 1:K(j)
290 idx = p1cidx(j, k, j, nj+1, h);
291 ub(idx) = 0;
292 end
293 end
294 end
295 end
296
297 %% CEQU: C(j,k,j) = Q(j,k) for all j,k
298 if verbose; fprintf(' CEQU constraints...\n'); end
299 for j = 1:M
300 for k = 1:K(j)
301 row = zeros(1, nVars);
302 row(Cidx(j, k, j)) = 1;
303 row(Qidx(j, k)) = -1;
304 Aeq = [Aeq; row];
305 beq = [beq; 0];
306 end
307 end
308
309 %% ONE1: sum over kj,hi,ni of (p1 + p1c) = 1 for each (j,i)
310 if verbose; fprintf(' ONE1 constraints...\n'); end
311 for j = 1:M
312 for i = 1:M
313 row = zeros(1, nVars);
314 for kj = 1:K(j)
315 for hi = 1:K(i)
316 for ni = 0:N
317 row(p1idx(j, kj, i, ni+1, hi)) = 1;
318 row(p1cidx(j, kj, i, ni+1, hi)) = 1;
319 end
320 end
321 end
322 Aeq = [Aeq; row];
323 beq = [beq; 1];
324 end
325 end
326
327 %% UTLB: U(i,k) = sum over t,nt,h of p1(i,k,t,nt,h) for each (i,k,t)
328 if verbose; fprintf(' UTLB constraints...\n'); end
329 for i = 1:M
330 for k = 1:K(i)
331 for t = 1:M
332 row = zeros(1, nVars);
333 row(Uidx(i, k)) = 1;
334 for nt = 0:N
335 for h = 1:K(t)
336 row(p1idx(i, k, t, nt+1, h)) = -1;
337 end
338 end
339 Aeq = [Aeq; row];
340 beq = [beq; 0];
341 end
342 end
343 end
344
345 %% UTLC: IT(i,k) = sum over t,nt,h of p1c(i,k,t,nt,h) for each (i,k,t)
346 if verbose; fprintf(' UTLC constraints...\n'); end
347 for i = 1:M
348 for k = 1:K(i)
349 for t = 1:M
350 row = zeros(1, nVars);
351 row(ITidx(i, k)) = 1;
352 for nt = 0:N
353 for h = 1:K(t)
354 row(p1cidx(i, k, t, nt+1, h)) = -1;
355 end
356 end
357 Aeq = [Aeq; row];
358 beq = [beq; 0];
359 end
360 end
361 end
362
363 %% QLEN: Q(i,k) = sum over ni of ni*p1(i,k,i,ni,k) for each (i,k)
364 if verbose; fprintf(' QLEN constraints...\n'); end
365 for i = 1:M
366 for k = 1:K(i)
367 row = zeros(1, nVars);
368 row(Qidx(i, k)) = 1;
369 for ni = 0:N
370 row(p1idx(i, k, i, ni+1, k)) = row(p1idx(i, k, i, ni+1, k)) - ni;
371 end
372 Aeq = [Aeq; row];
373 beq = [beq; 0];
374 end
375 end
376
377 %% CLEN: C(j,k,i) = sum over ni,h of ni*p1(j,k,i,ni,h) for each (j,k,i)
378 if verbose; fprintf(' CLEN constraints...\n'); end
379 for j = 1:M
380 for k = 1:K(j)
381 for i = 1:M
382 row = zeros(1, nVars);
383 row(Cidx(j, k, i)) = 1;
384 for ni = 0:N
385 for h = 1:K(i)
386 row(p1idx(j, k, i, ni+1, h)) = row(p1idx(j, k, i, ni+1, h)) - ni;
387 end
388 end
389 Aeq = [Aeq; row];
390 beq = [beq; 0];
391 end
392 end
393 end
394
395 %% ONE: sum over k of (U(j,k) + IT(j,k)) = 1 for each j
396 if verbose; fprintf(' ONE constraints...\n'); end
397 for j = 1:M
398 row = zeros(1, nVars);
399 for k = 1:K(j)
400 row(Uidx(j, k)) = 1;
401 row(ITidx(j, k)) = 1;
402 end
403 Aeq = [Aeq; row];
404 beq = [beq; 1];
405 end
406
407 %% POPC: sum over i,k of Q(i,k) = N
408 if verbose; fprintf(' POPC constraint...\n'); end
409 row = zeros(1, nVars);
410 for i = 1:M
411 for k = 1:K(i)
412 row(Qidx(i, k)) = 1;
413 end
414 end
415 Aeq = [Aeq; row];
416 beq = [beq; N];
417
418 %% MPCB: sum over i of C(j,k,i) = N*U(j,k) for each (j,k)
419 if verbose; fprintf(' MPCB constraints...\n'); end
420 for j = 1:M
421 for k = 1:K(j)
422 row = zeros(1, nVars);
423 for i = 1:M
424 row(Cidx(j, k, i)) = 1;
425 end
426 row(Uidx(j, k)) = -N;
427 Aeq = [Aeq; row];
428 beq = [beq; 0];
429 end
430 end
431
432 %% SRVB: phase balance at each station (AMPL THM1)
433 % sum{j,h} q{i,j}(k,h)*U(i,k) = sum{j,h} q{i,j}(h,k)*U(i,h) for each (i,k).
434 % This is the only family that reads the transition rates into the U
435 % variables; without it the phase split of each utilization is free.
436 % Stations with a single phase give an identically zero row, so skip them.
437 if verbose; fprintf(' SRVB constraints...\n'); end
438 for i = 1:M
439 if K(i) < 2
440 continue;
441 end
442 for k = 1:K(i)
443 row = zeros(1, nVars);
444 for j = 1:M
445 for h = 1:K(i)
446 row(Uidx(i, k)) = row(Uidx(i, k)) + q{i,j}(k, h);
447 row(Uidx(i, h)) = row(Uidx(i, h)) - q{i,j}(h, k);
448 end
449 end
450 Aeq = [Aeq; row];
451 beq = [beq; 0];
452 end
453 end
454
455 %% UJNT: joint-probability symmetry (AMPL SIMMETRY, projected onto p1)
456 % sum{ni>=1} p1(j,k,i,ni,h) = sum{nj>=1} p1(i,h,j,nj,k): both sides are
457 % P(n_j>=1, phase_j=k, n_i>=1, phase_i=h). Emitted once per unordered pair.
458 if verbose; fprintf(' UJNT constraints...\n'); end
459 for j = 1:M
460 for k = 1:K(j)
461 for i = 1:M
462 for h = 1:K(i)
463 if j < i || (j == i && k < h)
464 row = zeros(1, nVars);
465 for ni = 1:N
466 idx = p1idx(j, k, i, ni+1, h);
467 row(idx) = row(idx) + 1;
468 end
469 for nj = 1:N
470 idx = p1idx(i, h, j, nj+1, k);
471 row(idx) = row(idx) - 1;
472 end
473 Aeq = [Aeq; row];
474 beq = [beq; 0];
475 end
476 end
477 end
478 end
479 end
480
481 %% GFFL0: level-crossing balance at an empty station, per phase (AMPL THM30)
482 if verbose; fprintf(' GFFL0 constraints...\n'); end
483 for i = 1:M
484 for u = 1:K(i)
485 row = zeros(1, nVars);
486 for j = 1:M
487 if j ~= i
488 for k = 1:K(j)
489 for h = 1:K(j)
490 idx = p1idx(j, k, i, 0+1, u);
491 row(idx) = row(idx) + q{j,i}(k, h);
492 end
493 end
494 for k = 1:K(i)
495 idx = p1idx(i, k, i, 1+1, k);
496 row(idx) = row(idx) - q{i,j}(k, u);
497 end
498 end
499 end
500 Aeq = [Aeq; row];
501 beq = [beq; 0];
502 end
503 end
504
505 %% GFFL: level-crossing balance between n_i and n_i+1 (AMPL THM3)
506 % Rate of arrivals to station i while it holds ni jobs equals the rate of
507 % completions at i while it holds ni+1. This is the family that ties each
508 % station's arrival rate to its departure rate; with it absent, a station
509 % idle with probability one is feasible and the utilization lower bound
510 % collapses to zero.
511 if verbose; fprintf(' GFFL constraints...\n'); end
512 for i = 1:M
513 for ni = 0:(N-1)
514 row = zeros(1, nVars);
515 for j = 1:M
516 if j ~= i
517 for k = 1:K(j)
518 for h = 1:K(j)
519 for u = 1:K(i)
520 idx = p1idx(j, k, i, ni+1, u);
521 row(idx) = row(idx) + q{j,i}(k, h);
522 end
523 end
524 end
525 for k = 1:K(i)
526 for h = 1:K(i)
527 idx = p1idx(i, k, i, ni+1+1, k);
528 row(idx) = row(idx) - q{i,j}(k, h);
529 end
530 end
531 end
532 end
533 Aeq = [Aeq; row];
534 beq = [beq; 0];
535 end
536 end
537
538 %% QBAL: throughput balance at each station
539 % sum{j~=i} departure rate from i = sum{j~=i} arrival rate to i, with the
540 % arrival side split over whether station i is busy or empty.
541 if verbose; fprintf(' QBAL constraints...\n'); end
542 for i = 1:M
543 row = zeros(1, nVars);
544 for j = 1:M
545 if j ~= i
546 for k = 1:K(i)
547 for h = 1:K(i)
548 row(Uidx(i, k)) = row(Uidx(i, k)) + q{i,j}(k, h);
549 end
550 end
551 for k = 1:K(j)
552 for h = 1:K(j)
553 w = q{j,i}(k, h);
554 for u = 1:K(i)
555 for nj = 1:N
556 idx = p1idx(i, u, j, nj+1, k);
557 row(idx) = row(idx) - w;
558 end
559 end
560 for u = 1:K(i)
561 idx = p1idx(j, k, i, 0+1, u);
562 row(idx) = row(idx) - w;
563 end
564 end
565 end
566 end
567 end
568 Aeq = [Aeq; row];
569 beq = [beq; 0];
570 end
571
572 %% UUB1: sum over k of U(i,k) <= 1 for each i (inequality)
573 if verbose; fprintf(' UUB1 constraints...\n'); end
574 for i = 1:M
575 row = zeros(1, nVars);
576 for k = 1:K(i)
577 row(Uidx(i, k)) = 1;
578 end
579 Aineq = [Aineq; row];
580 bineq = [bineq; 1];
581 end
582
583 %% QUB1: Q(j,k) <= N*U(j,k) for each (j,k) (inequality)
584 if verbose; fprintf(' QUB1 constraints...\n'); end
585 for j = 1:M
586 for k = 1:K(j)
587 row = zeros(1, nVars);
588 row(Qidx(j, k)) = 1;
589 row(Uidx(j, k)) = -N;
590 Aineq = [Aineq; row];
591 bineq = [bineq; 0];
592 end
593 end
594
595 %% Build objective function
596 if verbose; fprintf('Building objective function...\n'); end
597 c = zeros(nVars, 1);
598 c(Uidx(objective_queue, objective_phase)) = 1;
599
600 if strcmp(sense, 'max')
601 c = -c;
602 end
603
604 %% Solve LP
605 if verbose
606 fprintf('Solving LP with %d variables and %d equality + %d inequality constraints...\n', ...
607 nVars, size(Aeq, 1), size(Aineq, 1));
608 end
609
610 % LP algorithm. R2025a's default 'dual-simplex-highs' is broken in some
611 % installs (errors "Unrecognized field name optimstatus"), so an
612 % interior-point variant is required. 'interior-point-legacy' is more
613 % accurate on loosely constrained instances, but once the balance families
614 % are present it declares the (feasible) system infeasible with exitflag -2
615 % and returns the bound box -- e.g. U1 in [0,1] instead of the exact
616 % [0.75,0.75] on the K=1 symmetric tandem. 'interior-point' solves those to
617 % ~1e-6 and agrees with the GLPK optimum of the reference AMPL model, so it
618 % is the default. Override via params.lpAlgorithm.
619 if isfield(params, 'lpAlgorithm') && ~isempty(params.lpAlgorithm)
620 lpAlgorithm = params.lpAlgorithm;
621 else
622 lpAlgorithm = 'interior-point';
623 end
624 if verbose
625 options = optimoptions('linprog', 'Display', 'final', 'Algorithm', lpAlgorithm);
626 else
627 options = optimoptions('linprog', 'Display', 'off', 'Algorithm', lpAlgorithm);
628 end
629
630 [x, fval, exitflag] = linprog(c, Aineq, bineq, Aeq, beq, lb, ub, options);
631
632 if strcmp(sense, 'max')
633 fval = -fval;
634 end
635
636 %% Extract results
637 result = struct();
638 result.objective = fval;
639 result.exitflag = exitflag;
640
641 % Whether the metric fields below can be filled is a property of the
642 % solution vector, not of exitflag. exitflag 0 (iteration limit) still
643 % returns a usable interior point -- that is the normal outcome on badly
644 % scaled instances such as the reference BAS network -- whereas a solve that
645 % breaks down returns an empty or non-finite x, which must never be
646 % consumed. Predicate on x accordingly, and say so rather than skipping
647 % silently. (Observed here: exitflag -4 comes back with x empty.)
648 hasSolution = ~isempty(x) && all(isfinite(x));
649 if ~isempty(x) && ~hasSolution
650 warning('mapqn_bnd_lr:nonFiniteSolution', ...
651 'linprog returned a non-finite solution (exitflag %d); U and the other metric fields are left unpopulated.', ...
652 exitflag);
653 end
654
655 if hasSolution
656 % Compute utilizations
657 result.U = zeros(M, maxK);
658 result.IT = zeros(M, maxK);
659 result.Q = zeros(M, maxK);
660
661 for i = 1:M
662 for k = 1:K(i)
663 result.U(i, k) = x(Uidx(i, k));
664 result.IT(i, k) = x(ITidx(i, k));
665 result.Q(i, k) = x(Qidx(i, k));
666 end
667 end
668 end
669
670 if verbose; fprintf('\n=== Results ===\n'); end
671 if verbose; fprintf('Objective value: %f\n', fval); end
672 if verbose; fprintf('Exit flag: %d\n', exitflag); end
673 if hasSolution && verbose
674 fprintf('\nUtilizations:\n');
675 for i = 1:M
676 fprintf(' Queue %d: U = [', i);
677 for k = 1:K(i)
678 fprintf('%.6f ', result.U(i, k));
679 end
680 fprintf(']\n');
681 end
682 fprintf('\nIdle times:\n');
683 for i = 1:M
684 fprintf(' Queue %d: IT = [', i);
685 for k = 1:K(i)
686 fprintf('%.6f ', result.IT(i, k));
687 end
688 fprintf(']\n');
689 end
690 fprintf('\nQueue lengths:\n');
691 for i = 1:M
692 fprintf(' Queue %d: Q = [', i);
693 for k = 1:K(i)
694 fprintf('%.6f ', result.Q(i, k));
695 end
696 fprintf(']\n');
697 end
698 end
699end
Definition Station.m:245