LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
mapqn_bnd_qr.m
1function [result, x, fval, exitflag] = mapqn_bnd_qr(params, objective_queue, objective_phase, sense)
2% MAPQN_BND_QR - General Quadratic Reduction Bounds for MAP Queueing Networks
3%
4% MATLAB port of the Python file bnd_qr.py
5%
6% Usage:
7% [result, x, fval, exitflag] = mapqn_bnd_qr(params)
8% [result, x, fval, exitflag] = mapqn_bnd_qr(params, objective_queue)
9% [result, x, fval, exitflag] = mapqn_bnd_qr(params, objective_queue, objective_phase)
10% [result, x, fval, exitflag] = mapqn_bnd_qr(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% .getP2 - Function handle to extract p2 values
34% x - Raw solution vector
35% fval - Objective function value
36% exitflag - Solver exit flag
37
38 if nargin < 2 || isempty(objective_queue)
39 objective_queue = 1;
40 end
41 if nargin < 3 || isempty(objective_phase)
42 objective_phase = 1;
43 end
44 if nargin < 4 || isempty(sense)
45 sense = 'max';
46 end
47
48 % Extract parameters
49 M = params.M;
50 N = params.N;
51 K = params.K(:);
52 mu = params.mu;
53 v = params.v;
54 r = params.r;
55 if isfield(params, 'verbose')
56 verbose = params.verbose;
57 else
58 verbose = true;
59 end
60
61 maxK = max(K);
62
63 % Compute transition rates q{i,j}(k,h)
64 q = cell(M, M);
65 for i = 1:M
66 for j = 1:M
67 q{i,j} = zeros(K(i), K(i));
68 for ki = 1:K(i)
69 for hi = 1:K(i)
70 if j ~= i
71 q{i,j}(ki, hi) = r(i,j) * mu{i}(ki, hi);
72 else
73 q{i,j}(ki, hi) = v{i}(ki, hi) + r(i,i) * mu{i}(ki, hi);
74 end
75 end
76 end
77 end
78 end
79
80 %% Build variable indexing
81 if verbose; fprintf('Building variable index map...\n'); end
82 varCount = 0;
83
84 % U(i,k) variables: utilization at queue i, phase k
85 Uidx = zeros(M, maxK);
86 for i = 1:M
87 for k = 1:K(i)
88 varCount = varCount + 1;
89 Uidx(i, k) = varCount;
90 end
91 end
92
93 % IT(i,k) variables: idle time at queue i, phase k
94 ITidx = zeros(M, maxK);
95 for i = 1:M
96 for k = 1:K(i)
97 varCount = varCount + 1;
98 ITidx(i, k) = varCount;
99 end
100 end
101
102 % UP(j,k,i,h) variables: utilization products
103 UPidx = zeros(M, maxK, M, maxK);
104 for j = 1:M
105 for kj = 1:K(j)
106 for i = 1:M
107 for hi = 1:K(i)
108 varCount = varCount + 1;
109 UPidx(j, kj, i, hi) = varCount;
110 end
111 end
112 end
113 end
114
115 % QP(j,k,i,h) variables: queue-length products
116 QPidx = zeros(M, maxK, M, maxK);
117 for j = 1:M
118 for kj = 1:K(j)
119 for i = 1:M
120 for hi = 1:K(i)
121 varCount = varCount + 1;
122 QPidx(j, kj, i, hi) = varCount;
123 end
124 end
125 end
126 end
127
128 % Q(i,k) variables: mean queue length
129 Qidx = zeros(M, maxK);
130 for i = 1:M
131 for k = 1:K(i)
132 varCount = varCount + 1;
133 Qidx(i, k) = varCount;
134 end
135 end
136
137 % C(j,k,i) variables: conditional queue lengths
138 Cidx = zeros(M, maxK, M);
139 for j = 1:M
140 for kj = 1:K(j)
141 for i = 1:M
142 varCount = varCount + 1;
143 Cidx(j, kj, i) = varCount;
144 end
145 end
146 end
147
148 % I_var(j,k,i) variables: conditional idle lengths
149 Iidx = zeros(M, maxK, M);
150 for j = 1:M
151 for kj = 1:K(j)
152 for i = 1:M
153 varCount = varCount + 1;
154 Iidx(j, kj, i) = varCount;
155 end
156 end
157 end
158
159 % p1(j,k,i,ni,h) variables: marginal probabilities, ni from 0 to N
160 % Stored as p1idx(j, k, i, ni+1, h)
161 p1idx = zeros(M, maxK, M, N+1, maxK);
162 for j = 1:M
163 for kj = 1:K(j)
164 for i = 1:M
165 for ni = 0:N
166 for hi = 1:K(i)
167 varCount = varCount + 1;
168 p1idx(j, kj, i, ni+1, hi) = varCount;
169 end
170 end
171 end
172 end
173 end
174
175 % p1c(j,k,i,ni,h) variables: complementary marginal probabilities, ni from 0 to N
176 % Stored as p1cidx(j, k, i, ni+1, h)
177 p1cidx = zeros(M, maxK, M, N+1, maxK);
178 for j = 1:M
179 for kj = 1:K(j)
180 for i = 1:M
181 for ni = 0:N
182 for hi = 1:K(i)
183 varCount = varCount + 1;
184 p1cidx(j, kj, i, ni+1, hi) = varCount;
185 end
186 end
187 end
188 end
189 end
190
191 % p2(j,nj,k,i,ni,h) variables: joint probabilities, nj from 0 to N, ni from 0 to N
192 % Stored as p2idx(j, nj+1, k, i, ni+1, h)
193 p2idx = zeros(M, N+1, maxK, M, N+1, maxK);
194 for j = 1:M
195 for nj = 0:N
196 for kj = 1:K(j)
197 for i = 1:M
198 for ni = 0:N
199 for hi = 1:K(i)
200 varCount = varCount + 1;
201 p2idx(j, nj+1, kj, i, ni+1, hi) = varCount;
202 end
203 end
204 end
205 end
206 end
207 end
208
209 nVars = varCount;
210 if verbose; fprintf('Total variables: %d\n', nVars); end
211
212 %% Initialize bounds
213 lb = zeros(nVars, 1);
214 ub = inf(nVars, 1);
215
216 % Set upper bounds for each variable type
217 for i = 1:M
218 for k = 1:K(i)
219 ub(Uidx(i, k)) = 1;
220 ub(ITidx(i, k)) = 1;
221 ub(Qidx(i, k)) = N;
222 end
223 end
224 for j = 1:M
225 for kj = 1:K(j)
226 for i = 1:M
227 for hi = 1:K(i)
228 ub(UPidx(j, kj, i, hi)) = 1;
229 ub(QPidx(j, kj, i, hi)) = N;
230 end
231 end
232 end
233 end
234 for j = 1:M
235 for kj = 1:K(j)
236 for i = 1:M
237 ub(Cidx(j, kj, i)) = N;
238 ub(Iidx(j, kj, i)) = N;
239 end
240 end
241 end
242 for j = 1:M
243 for kj = 1:K(j)
244 for i = 1:M
245 for ni = 0:N
246 for hi = 1:K(i)
247 ub(p1idx(j, kj, i, ni+1, hi)) = 1;
248 ub(p1cidx(j, kj, i, ni+1, hi)) = 1;
249 end
250 end
251 end
252 end
253 end
254 for j = 1:M
255 for nj = 0:N
256 for kj = 1:K(j)
257 for i = 1:M
258 for ni = 0:N
259 for hi = 1:K(i)
260 ub(p2idx(j, nj+1, kj, i, ni+1, hi)) = 1;
261 end
262 end
263 end
264 end
265 end
266 end
267
268 %% Build constraints
269 Aeq = [];
270 beq = [];
271 Aineq = [];
272 bineq = [];
273
274 if verbose; fprintf('Building constraints...\n'); end
275
276 %% ZER1: p1(j,k,j,0,k) = 0 for all j,k (via upper bounds)
277 if verbose; fprintf(' ZER1 constraints...\n'); end
278 for j = 1:M
279 for k = 1:K(j)
280 idx = p1idx(j, k, j, 0+1, k);
281 ub(idx) = 0;
282 end
283 end
284
285 %% ZER2: p1(j,k,j,nj,h) = 0 for h ~= k, all j,k,nj (via upper bounds)
286 if verbose; fprintf(' ZER2 constraints...\n'); end
287 for j = 1:M
288 for k = 1:K(j)
289 for nj = 0:N
290 for h = 1:K(j)
291 if h ~= k
292 idx = p1idx(j, k, j, nj+1, h);
293 ub(idx) = 0;
294 end
295 end
296 end
297 end
298 end
299
300 %% ZER3: p1(j,k,i,N,h) = 0 for j ~= i, all j,k,i,h (via upper bounds)
301 if verbose; fprintf(' ZER3 constraints...\n'); end
302 for j = 1:M
303 for k = 1:K(j)
304 for i = 1:M
305 if j ~= i
306 for h = 1:K(i)
307 idx = p1idx(j, k, i, N+1, h);
308 ub(idx) = 0;
309 end
310 end
311 end
312 end
313 end
314
315 %% ZER4: p1c(j,k,j,nj,h) = 0 for nj >= 1, all j,k,nj,h (via upper bounds)
316 if verbose; fprintf(' ZER4 constraints...\n'); end
317 for j = 1:M
318 for k = 1:K(j)
319 for nj = 1:N
320 for h = 1:K(j)
321 idx = p1cidx(j, k, j, nj+1, h);
322 ub(idx) = 0;
323 end
324 end
325 end
326 end
327
328 %% ZER5: p2(j,nj,k,j,nj,h) = 0 for h ~= k (AMPL ZERO1) (via upper bounds)
329 % The joint variables carry the same structural zeros as their p1
330 % projections: a station cannot be in two phases at once, cannot hold two
331 % different populations at once, and two stations cannot jointly hold more
332 % than the closed population.
333 if verbose; fprintf(' ZER5 (p2 phase consistency) constraints...\n'); end
334 for j = 1:M
335 for nj = 0:N
336 for k = 1:K(j)
337 for h = 1:K(j)
338 if h ~= k
339 ub(p2idx(j, nj+1, k, j, nj+1, h)) = 0;
340 end
341 end
342 end
343 end
344 end
345
346 %% ZER6: p2(j,nj,k,j,ni,h) = 0 for ni ~= nj (AMPL ZERO2) (via upper bounds)
347 if verbose; fprintf(' ZER6 (p2 population consistency) constraints...\n'); end
348 for j = 1:M
349 for nj = 0:N
350 for k = 1:K(j)
351 for ni = 0:N
352 if ni ~= nj
353 for h = 1:K(j)
354 ub(p2idx(j, nj+1, k, j, ni+1, h)) = 0;
355 end
356 end
357 end
358 end
359 end
360 end
361
362 %% ZER7: p2(j,nj,k,i,ni,h) = 0 for i ~= j and nj+ni > N (AMPL ZERO3)
363 if verbose; fprintf(' ZER7 (p2 population cap) constraints...\n'); end
364 for j = 1:M
365 for nj = 0:N
366 for k = 1:K(j)
367 for i = 1:M
368 if i ~= j
369 for ni = 0:N
370 if nj + ni > N
371 for h = 1:K(i)
372 ub(p2idx(j, nj+1, k, i, ni+1, h)) = 0;
373 end
374 end
375 end
376 end
377 end
378 end
379 end
380 end
381
382 %% CEQU: C(j,k,j) = Q(j,k) for all j,k
383 if verbose; fprintf(' CEQU constraints...\n'); end
384 for j = 1:M
385 for k = 1:K(j)
386 row = zeros(1, nVars);
387 row(Cidx(j, k, j)) = 1;
388 row(Qidx(j, k)) = -1;
389 Aeq = [Aeq; row];
390 beq = [beq; 0];
391 end
392 end
393
394 %% ONE1: sum over kj,hi,ni of (p1 + p1c) = 1 for each (j,i)
395 if verbose; fprintf(' ONE1 constraints...\n'); end
396 for j = 1:M
397 for i = 1:M
398 row = zeros(1, nVars);
399 for kj = 1:K(j)
400 for hi = 1:K(i)
401 for ni = 0:N
402 row(p1idx(j, kj, i, ni+1, hi)) = 1;
403 row(p1cidx(j, kj, i, ni+1, hi)) = 1;
404 end
405 end
406 end
407 Aeq = [Aeq; row];
408 beq = [beq; 1];
409 end
410 end
411
412 %% UTLB: U(i,k) = sum over t,nt,h of p1(i,k,t,nt,h) for each (i,k,t)
413 if verbose; fprintf(' UTLB constraints...\n'); end
414 for i = 1:M
415 for k = 1:K(i)
416 for t = 1:M
417 row = zeros(1, nVars);
418 row(Uidx(i, k)) = 1;
419 for nt = 0:N
420 for h = 1:K(t)
421 row(p1idx(i, k, t, nt+1, h)) = -1;
422 end
423 end
424 Aeq = [Aeq; row];
425 beq = [beq; 0];
426 end
427 end
428 end
429
430 %% UTLC: IT(i,k) = sum over t,nt,h of p1c(i,k,t,nt,h) for each (i,k,t)
431 if verbose; fprintf(' UTLC constraints...\n'); end
432 for i = 1:M
433 for k = 1:K(i)
434 for t = 1:M
435 row = zeros(1, nVars);
436 row(ITidx(i, k)) = 1;
437 for nt = 0:N
438 for h = 1:K(t)
439 row(p1cidx(i, k, t, nt+1, h)) = -1;
440 end
441 end
442 Aeq = [Aeq; row];
443 beq = [beq; 0];
444 end
445 end
446 end
447
448 %% QLEN: Q(i,k) = sum over ni of ni*p1(i,k,i,ni,k) for each (i,k)
449 if verbose; fprintf(' QLEN constraints...\n'); end
450 for i = 1:M
451 for k = 1:K(i)
452 row = zeros(1, nVars);
453 row(Qidx(i, k)) = 1;
454 for ni = 0:N
455 row(p1idx(i, k, i, ni+1, k)) = row(p1idx(i, k, i, ni+1, k)) - ni;
456 end
457 Aeq = [Aeq; row];
458 beq = [beq; 0];
459 end
460 end
461
462 %% SRVB: Service balance
463 % 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)
464 % Stations with a single phase give an identically zero row, so skip them.
465 if verbose; fprintf(' SRVB constraints...\n'); end
466 for i = 1:M
467 if K(i) < 2
468 continue;
469 end
470 for k = 1:K(i)
471 row = zeros(1, nVars);
472 for j = 1:M
473 for h = 1:K(i)
474 row(Uidx(i, k)) = row(Uidx(i, k)) + q{i,j}(k, h);
475 row(Uidx(i, h)) = row(Uidx(i, h)) - q{i,j}(h, k);
476 end
477 end
478 Aeq = [Aeq; row];
479 beq = [beq; 0];
480 end
481 end
482
483 %% POPC: sum over i,k of Q(i,k) = N
484 if verbose; fprintf(' POPC constraint...\n'); end
485 row = zeros(1, nVars);
486 for i = 1:M
487 for k = 1:K(i)
488 row(Qidx(i, k)) = 1;
489 end
490 end
491 Aeq = [Aeq; row];
492 beq = [beq; N];
493
494 %% ONE: sum over k of (U(j,k) + IT(j,k)) = 1 for each j
495 if verbose; fprintf(' ONE constraints...\n'); end
496 for j = 1:M
497 row = zeros(1, nVars);
498 for k = 1:K(j)
499 row(Uidx(j, k)) = 1;
500 row(ITidx(j, k)) = 1;
501 end
502 Aeq = [Aeq; row];
503 beq = [beq; 1];
504 end
505
506 %% PCL2: sum over i,j,ni>=1,nj>=1,h,k of ni*nj*p2(i,ni,h,j,nj,k) = N^2
507 if verbose; fprintf(' PCL2 constraint...\n'); end
508 row = zeros(1, nVars);
509 for i = 1:M
510 for j = 1:M
511 for ni = 1:N
512 for nj = 1:N
513 for h = 1:K(i)
514 for k = 1:K(j)
515 idx = p2idx(i, ni+1, h, j, nj+1, k);
516 row(idx) = row(idx) + ni * nj;
517 end
518 end
519 end
520 end
521 end
522 end
523 Aeq = [Aeq; row];
524 beq = [beq; N^2];
525
526 %% PI21: p1(j,k,i,ni,h) = sum over nj=1..N of p2(j,nj,k,i,ni,h)
527 if verbose; fprintf(' PI21 constraints...\n'); end
528 for j = 1:M
529 for k = 1:K(j)
530 for i = 1:M
531 for ni = 0:N
532 for h = 1:K(i)
533 row = zeros(1, nVars);
534 row(p1idx(j, k, i, ni+1, h)) = 1;
535 for nj = 1:N
536 idx = p2idx(j, nj+1, k, i, ni+1, h);
537 row(idx) = row(idx) - 1;
538 end
539 Aeq = [Aeq; row];
540 beq = [beq; 0];
541 end
542 end
543 end
544 end
545 end
546
547 %% PI22: p1c(j,k,i,ni,h) = p2(j,0,k,i,ni,h)
548 if verbose; fprintf(' PI22 constraints...\n'); end
549 for j = 1:M
550 for k = 1:K(j)
551 for i = 1:M
552 for ni = 0:N
553 for h = 1:K(i)
554 row = zeros(1, nVars);
555 row(p1cidx(j, k, i, ni+1, h)) = 1;
556 row(p2idx(j, 0+1, k, i, ni+1, h)) = -1;
557 Aeq = [Aeq; row];
558 beq = [beq; 0];
559 end
560 end
561 end
562 end
563 end
564
565 %% PI23: p2(i,ni,h,j,nj,k) = p2(j,nj,k,i,ni,h) (symmetry)
566 % Only generate for j < i, or (j == i and nj < ni), to avoid redundancy
567 if verbose; fprintf(' PI23 (symmetry) constraints...\n'); end
568 for j = 1:M
569 for nj = 0:N
570 for k = 1:K(j)
571 for i = 1:M
572 for ni = 0:N
573 for h = 1:K(i)
574 % Only generate constraint if (j,nj,k) < (i,ni,h) in lex order
575 if j < i || (j == i && nj < ni) || (j == i && nj == ni && k < h)
576 idx1 = p2idx(i, ni+1, h, j, nj+1, k);
577 idx2 = p2idx(j, nj+1, k, i, ni+1, h);
578 if idx1 ~= idx2
579 row = zeros(1, nVars);
580 row(idx1) = 1;
581 row(idx2) = -1;
582 Aeq = [Aeq; row];
583 beq = [beq; 0];
584 end
585 end
586 end
587 end
588 end
589 end
590 end
591 end
592
593 %% CLEN: C(j,k,i) = sum over ni,h of ni*p1(j,k,i,ni,h) for each (j,k,i)
594 % Without this the C variables are defined only on the diagonal by CEQU,
595 % which leaves CUB1/CUB2 vacuous for i ~= j.
596 if verbose; fprintf(' CLEN constraints...\n'); end
597 for j = 1:M
598 for k = 1:K(j)
599 for i = 1:M
600 row = zeros(1, nVars);
601 row(Cidx(j, k, i)) = 1;
602 for ni = 0:N
603 for h = 1:K(i)
604 idx = p1idx(j, k, i, ni+1, h);
605 row(idx) = row(idx) - ni;
606 end
607 end
608 Aeq = [Aeq; row];
609 beq = [beq; 0];
610 end
611 end
612 end
613
614 %% MARG: p2(j,nj,k,j,nj,k) = sum over ni<=N-nj,h of p2(j,nj,k,i,ni,h)
615 % AMPL MARGINALS. ONE1 only imposes the aggregate over nj, so the
616 % per-population marginal consistency is a separate family.
617 if verbose; fprintf(' MARG constraints...\n'); end
618 for j = 1:M
619 for k = 1:K(j)
620 for nj = 0:N
621 for i = 1:M
622 if i ~= j
623 row = zeros(1, nVars);
624 row(p2idx(j, nj+1, k, j, nj+1, k)) = 1;
625 for ni = 0:(N-nj)
626 for h = 1:K(i)
627 idx = p2idx(j, nj+1, k, i, ni+1, h);
628 row(idx) = row(idx) - 1;
629 end
630 end
631 Aeq = [Aeq; row];
632 beq = [beq; 0];
633 end
634 end
635 end
636 end
637 end
638
639 %% THM2: sum over i,ni>=1,h of ni*p2(j,nj,k,i,ni,h) = N*p2(j,nj,k,j,nj,k)
640 % AMPL THM2, the queue-length theorem conditioned on (j,nj,k). Summing it
641 % over nj >= 1 recovers the aggregate form sum_i C(j,k,i) = N*U(j,k).
642 if verbose; fprintf(' THM2 constraints...\n'); end
643 for j = 1:M
644 for k = 1:K(j)
645 for nj = 0:N
646 row = zeros(1, nVars);
647 for i = 1:M
648 for ni = 1:N
649 for h = 1:K(i)
650 idx = p2idx(j, nj+1, k, i, ni+1, h);
651 row(idx) = row(idx) + ni;
652 end
653 end
654 end
655 idx = p2idx(j, nj+1, k, j, nj+1, k);
656 row(idx) = row(idx) - N;
657 Aeq = [Aeq; row];
658 beq = [beq; 0];
659 end
660 end
661 end
662
663 %% THM30: level-crossing balance at an empty station, per arrival phase
664 % AMPL THM30. Rate into {n_i = 0, phase_i = u} from a busy neighbour equals
665 % the rate out of {n_i = 1} through a completion at i.
666 if verbose; fprintf(' THM30 constraints...\n'); end
667 for i = 1:M
668 for u = 1:K(i)
669 row = zeros(1, nVars);
670 for j = 1:M
671 if j ~= i
672 for nj = 1:N
673 for k = 1:K(j)
674 for h = 1:K(j)
675 idx = p2idx(j, nj+1, k, i, 0+1, u);
676 row(idx) = row(idx) + q{j,i}(k, h);
677 end
678 end
679 end
680 for nj = 0:N
681 for k = 1:K(i)
682 for h = 1:K(j)
683 idx = p2idx(j, nj+1, h, i, 1+1, k);
684 row(idx) = row(idx) - q{i,j}(k, u);
685 end
686 end
687 end
688 end
689 end
690 Aeq = [Aeq; row];
691 beq = [beq; 0];
692 end
693 end
694
695 %% THM3: level-crossing balance between n_i and n_i+1
696 % AMPL THM3. This is the family that ties station i's arrival rate to its
697 % departure rate; without it nothing prevents a station from being idle with
698 % probability one, which is why the minimum utilization collapsed to zero.
699 if verbose; fprintf(' THM3 constraints...\n'); end
700 for i = 1:M
701 for ni = 0:(N-1)
702 row = zeros(1, nVars);
703 for j = 1:M
704 if j ~= i
705 for nj = 1:N
706 for k = 1:K(j)
707 for h = 1:K(j)
708 for u = 1:K(i)
709 idx = p2idx(j, nj+1, k, i, ni+1, u);
710 row(idx) = row(idx) + q{j,i}(k, h);
711 end
712 end
713 end
714 end
715 for nj = 0:N
716 for k = 1:K(i)
717 for u = 1:K(j)
718 for h = 1:K(i)
719 idx = p2idx(j, nj+1, u, i, ni+1+1, k);
720 row(idx) = row(idx) - q{i,j}(k, h);
721 end
722 end
723 end
724 end
725 end
726 end
727 Aeq = [Aeq; row];
728 beq = [beq; 0];
729 end
730 end
731
732 %% UUB1: sum over k of U(i,k) <= 1 for each i (inequality)
733 if verbose; fprintf(' UUB1 constraints...\n'); end
734 for i = 1:M
735 row = zeros(1, nVars);
736 for k = 1:K(i)
737 row(Uidx(i, k)) = 1;
738 end
739 Aineq = [Aineq; row];
740 bineq = [bineq; 1];
741 end
742
743 %% QUB1: Q(j,k) <= N*U(j,k) for each (j,k) (inequality)
744 if verbose; fprintf(' QUB1 constraints...\n'); end
745 for j = 1:M
746 for k = 1:K(j)
747 row = zeros(1, nVars);
748 row(Qidx(j, k)) = 1;
749 row(Uidx(j, k)) = -N;
750 Aineq = [Aineq; row];
751 bineq = [bineq; 0];
752 end
753 end
754
755 %% CUB1: C(j,k,i) <= sum over h of Q(i,h) for each (j,k,i) (inequality)
756 if verbose; fprintf(' CUB1 constraints...\n'); end
757 for j = 1:M
758 for k = 1:K(j)
759 for i = 1:M
760 row = zeros(1, nVars);
761 row(Cidx(j, k, i)) = 1;
762 for h = 1:K(i)
763 row(Qidx(i, h)) = -1;
764 end
765 Aineq = [Aineq; row];
766 bineq = [bineq; 0];
767 end
768 end
769 end
770
771 %% CUB2: C(j,k,i) <= N*U(j,k) for each (j,k,i) (inequality)
772 if verbose; fprintf(' CUB2 constraints...\n'); end
773 for j = 1:M
774 for k = 1:K(j)
775 for i = 1:M
776 row = zeros(1, nVars);
777 row(Cidx(j, k, i)) = 1;
778 row(Uidx(j, k)) = -N;
779 Aineq = [Aineq; row];
780 bineq = [bineq; 0];
781 end
782 end
783 end
784
785 %% THM4: N*P(j busy in k, i nonempty) <= sum_t C(j,k,t) (inequality)
786 % AMPL THM4.
787 if verbose; fprintf(' THM4 constraints...\n'); end
788 for j = 1:M
789 for k = 1:K(j)
790 for i = 1:M
791 row = zeros(1, nVars);
792 for h = 1:K(i)
793 for nj = 0:N
794 for ni = 1:N
795 idx = p2idx(j, nj+1, k, i, ni+1, h);
796 row(idx) = row(idx) + N;
797 end
798 end
799 end
800 for t = 1:M
801 for h = 1:K(t)
802 for nj = 0:N
803 for nt = 0:N
804 idx = p2idx(j, nj+1, k, t, nt+1, h);
805 row(idx) = row(idx) - nt;
806 end
807 end
808 end
809 end
810 Aineq = [Aineq; row];
811 bineq = [bineq; 0];
812 end
813 end
814 end
815
816 %% Build objective function
817 if verbose; fprintf('Building objective function...\n'); end
818 c = zeros(nVars, 1);
819 c(Uidx(objective_queue, objective_phase)) = 1;
820
821 if strcmp(sense, 'max')
822 c = -c;
823 end
824
825 %% Solve LP
826 if verbose
827 fprintf('Solving LP with %d variables and %d equality + %d inequality constraints...\n', ...
828 nVars, size(Aeq, 1), size(Aineq, 1));
829 end
830
831 % LP algorithm. R2025a's default 'dual-simplex-highs' is broken in some
832 % installs (errors "Unrecognized field name optimstatus"), so an
833 % interior-point variant is required. 'interior-point-legacy' is more
834 % accurate on loosely constrained instances, but once the balance families
835 % are present it declares the (feasible) system infeasible with exitflag -2
836 % and returns the bound box -- e.g. U1 in [0,1] instead of the exact
837 % [0.75,0.75] on the K=1 symmetric tandem. 'interior-point' solves those to
838 % ~1e-6 and agrees with the GLPK optimum of the reference AMPL model, so it
839 % is the default. Override via params.lpAlgorithm.
840 if isfield(params, 'lpAlgorithm') && ~isempty(params.lpAlgorithm)
841 lpAlgorithm = params.lpAlgorithm;
842 else
843 lpAlgorithm = 'interior-point';
844 end
845 if verbose
846 options = optimoptions('linprog', 'Display', 'final', 'Algorithm', lpAlgorithm);
847 else
848 options = optimoptions('linprog', 'Display', 'off', 'Algorithm', lpAlgorithm);
849 end
850
851 [x, fval, exitflag] = linprog(c, Aineq, bineq, Aeq, beq, lb, ub, options);
852
853 if strcmp(sense, 'max')
854 fval = -fval;
855 end
856
857 %% Extract results
858 result = struct();
859 result.objective = fval;
860 result.exitflag = exitflag;
861
862 % Whether the metric fields below can be filled is a property of the
863 % solution vector, not of exitflag. exitflag 0 (iteration limit) still
864 % returns a usable interior point -- that is the normal outcome on badly
865 % scaled instances such as the reference BAS network -- whereas a solve that
866 % breaks down returns an empty or non-finite x, which must never be
867 % consumed. Predicate on x accordingly, and say so rather than skipping
868 % silently. (Observed here: exitflag -4 comes back with x empty.)
869 hasSolution = ~isempty(x) && all(isfinite(x));
870 if ~isempty(x) && ~hasSolution
871 warning('mapqn_bnd_qr:nonFiniteSolution', ...
872 'linprog returned a non-finite solution (exitflag %d); U and the other metric fields are left unpopulated.', ...
873 exitflag);
874 end
875
876 if hasSolution
877 % Compute utilizations, idle times, queue lengths
878 result.U = zeros(M, maxK);
879 result.IT = zeros(M, maxK);
880 result.Q = zeros(M, maxK);
881
882 for i = 1:M
883 for k = 1:K(i)
884 result.U(i, k) = x(Uidx(i, k));
885 result.IT(i, k) = x(ITidx(i, k));
886 result.Q(i, k) = x(Qidx(i, k));
887 end
888 end
889
890 % Function handle to extract p2 values: p2(j,nj,k,i,ni,h)
891 result.getP2 = @(j, nj, k, i, ni, h) x(p2idx(j, nj+1, k, i, ni+1, h));
892 end
893
894 if verbose; fprintf('\n=== Results ===\n'); end
895 if verbose; fprintf('Objective value: %f\n', fval); end
896 if verbose; fprintf('Exit flag: %d\n', exitflag); end
897 if hasSolution && verbose
898 fprintf('\nUtilizations:\n');
899 for i = 1:M
900 fprintf(' Queue %d: U = [', i);
901 for k = 1:K(i)
902 fprintf('%.6f ', result.U(i, k));
903 end
904 fprintf(']\n');
905 end
906 fprintf('\nIdle times:\n');
907 for i = 1:M
908 fprintf(' Queue %d: IT = [', i);
909 for k = 1:K(i)
910 fprintf('%.6f ', result.IT(i, k));
911 end
912 fprintf(']\n');
913 end
914 fprintf('\nQueue lengths:\n');
915 for i = 1:M
916 fprintf(' Queue %d: Q = [', i);
917 for k = 1:K(i)
918 fprintf('%.6f ', result.Q(i, k));
919 end
920 fprintf(']\n');
921 end
922 end
923end
Definition Station.m:245