LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
qrf_rsrd.m
1function [result, x, fval, exitflag] = qrf_rsrd(params, objective, sense)
2% QRF_RSRD - Quadratic Reduction Framework for RS-RD blocking networks
3%
4% MATLAB port of the AMPL model qrboundsrsrd_skel.mod
5%
6% Usage:
7% [result, x, fval, exitflag] = qrf_rsrd(params)
8% [result, x, fval, exitflag] = qrf_rsrd(params, objective)
9% [result, x, fval, exitflag] = qrf_rsrd(params, objective, sense)
10%
11% Inputs:
12% params - Structure with model parameters:
13% .M - Number of queues
14% .N - Total population
15% .F - [M x 1] Capacity of each queue
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% .alpha - (optional) {M x 1} cell, each alpha{i} is [N x 1] load-dependent rates
21%
22% objective - (optional) 'U1min' (default), 'U1max', or queue index 1..M
23% sense - (optional) 'min' (default) or 'max'
24%
25% Outputs:
26% result - Structure with results:
27% .U - [M x 1] Utilization of each queue
28% .Ueff - [M x 1] Effective utilization
29% .pb - [M x 1] Blocking probability
30% .p2 - Decision variable tensor (marginal probabilities)
31% x - Raw solution vector
32% fval - Objective function value
33% exitflag - Solver exit flag
34
35 if nargin < 2 || isempty(objective)
36 objective = 'U1min';
37 end
38 if nargin < 3 || isempty(sense)
39 sense = 'min';
40 end
41
42 % Extract parameters
43 M = params.M;
44 N = params.N;
45 F = params.F(:);
46 K = params.K(:);
47 mu = params.mu;
48 v = params.v;
49 r = params.r;
50 if isfield(params, 'verbose')
51 verbose = params.verbose;
52 else
53 verbose = true;
54 end
55
56 % Default alpha (load-independent)
57 if isfield(params, 'alpha') && ~isempty(params.alpha)
58 alpha = params.alpha;
59 else
60 alpha = cell(M, 1);
61 for i = 1:M
62 alpha{i} = ones(N, 1);
63 end
64 end
65
66 % Compute transition rates q(i,j,k,h,n)
67 % q{i,j} is a K(i) x K(i) x (N+1) array
68 q = cell(M, M);
69 for i = 1:M
70 for j = 1:M
71 q{i,j} = zeros(K(i), K(i), N+1);
72 for ki = 1:K(i)
73 for hi = 1:K(i)
74 for n = 1:N % n=0 gives q=0
75 if j ~= i
76 q{i,j}(ki, hi, n+1) = r(i,j) * mu{i}(ki, hi) * alpha{i}(n);
77 else
78 q{i,j}(ki, hi, n+1) = (v{i}(ki, hi) + r(i,i) * mu{i}(ki, hi)) * alpha{i}(n);
79 end
80 end
81 end
82 end
83 end
84 end
85
86 %% Build variable indexing
87 % p2(j, nj, kj, i, ni, hi) for j in 1:M, nj in 0:F(j), kj in 1:K(j),
88 % i in 1:M, ni in 0:F(i), hi in 1:K(i)
89
90 % Count variables and build index map
91 if verbose; fprintf('Building variable index map...\n'); end
92 varCount = 0;
93 p2idx = cell(M, 1);
94 for j = 1:M
95 p2idx{j} = cell(F(j)+1, K(j), M);
96 for nj = 0:F(j)
97 for kj = 1:K(j)
98 for i = 1:M
99 p2idx{j}{nj+1, kj, i} = zeros(F(i)+1, K(i));
100 for ni = 0:F(i)
101 for hi = 1:K(i)
102 varCount = varCount + 1;
103 p2idx{j}{nj+1, kj, i}(ni+1, hi) = varCount;
104 end
105 end
106 end
107 end
108 end
109 end
110 nP2Vars = varCount;
111
112 % U and Ueff variables: U(i,k,ni) and Ueff(i,k,ni) for ni >= 1
113 Uidx = cell(M, 1);
114 Ueffidx = cell(M, 1);
115 for i = 1:M
116 Uidx{i} = zeros(K(i), F(i));
117 Ueffidx{i} = zeros(K(i), F(i));
118 for ki = 1:K(i)
119 for ni = 1:F(i)
120 varCount = varCount + 1;
121 Uidx{i}(ki, ni) = varCount;
122 varCount = varCount + 1;
123 Ueffidx{i}(ki, ni) = varCount;
124 end
125 end
126 end
127
128 % pb(i): blocking-probability variables, AMPL `var pb{i in 1..M} >=0, <=1`
129 pbidx = zeros(M, 1);
130 for i = 1:M
131 varCount = varCount + 1;
132 pbidx(i) = varCount;
133 end
134
135 nVars = varCount;
136 if verbose; fprintf('Total variables: %d (p2: %d, U/Ueff/pb: %d)\n', nVars, nP2Vars, nVars - nP2Vars); end
137
138 %% Build constraints
139 Aeq = [];
140 beq = [];
141 Aineq = [];
142 bineq = [];
143
144 % Helper to get variable index
145 getIdx = @(j, nj, kj, i, ni, hi) p2idx{j}{nj+1, kj, i}(ni+1, hi);
146 getUIdx = @(i, ki, ni) Uidx{i}(ki, ni);
147 getUeffIdx = @(i, ki, ni) Ueffidx{i}(ki, ni);
148
149 if verbose; fprintf('Building constraints...\n'); end
150
151 %% ONE: Normalization - sum over nj, kj equals 1 for each j
152 if verbose; fprintf(' ONE constraints...\n'); end
153 for j = 1:M
154 row = zeros(1, nVars);
155 for nj = 0:F(j)
156 for kj = 1:K(j)
157 idx = getIdx(j, nj, kj, j, nj, kj);
158 row(idx) = 1;
159 end
160 end
161 Aeq = [Aeq; row];
162 beq = [beq; 1];
163 end
164
165 %% ZERO constraints - fix infeasible states to zero
166 if verbose; fprintf(' ZERO constraints...\n'); end
167 lb = zeros(nVars, 1);
168 ub = ones(nVars, 1);
169 % U and Ueff bounds are [0, 1]
170 for i = 1:M
171 for ki = 1:K(i)
172 for ni = 1:F(i)
173 ub(getUIdx(i, ki, ni)) = 1;
174 ub(getUeffIdx(i, ki, ni)) = 1;
175 end
176 end
177 end
178
179 for j = 1:M
180 for nj = 0:F(j)
181 for kj = 1:K(j)
182 for i = 1:M
183 for ni = 0:F(i)
184 for hi = 1:K(i)
185 idx = getIdx(j, nj, kj, i, ni, hi);
186
187 % ZERO1: i==j, nj==ni, h<>k
188 if i == j && nj == ni && hi ~= kj
189 ub(idx) = 0;
190 end
191
192 % ZERO2: i==j, nj<>ni
193 if i == j && nj ~= ni
194 ub(idx) = 0;
195 end
196
197 % ZERO3: i<>j, nj+ni > N
198 if i ~= j && nj + ni > N
199 ub(idx) = 0;
200 end
201
202 % ZERO6: i<>j, N-nj-ni > sum of other capacities
203 if i ~= j
204 sumOtherF = sum(F) - F(i) - F(j);
205 if N - nj - ni > sumOtherF
206 ub(idx) = 0;
207 end
208 end
209 end
210 end
211 end
212 end
213 end
214
215 % ZERO7: N-nj > sum of other capacities (for diagonal)
216 for nj = 0:F(j)
217 for kj = 1:K(j)
218 sumOtherF = sum(F) - F(j);
219 if N - nj > sumOtherF
220 idx = getIdx(j, nj, kj, j, nj, kj);
221 ub(idx) = 0;
222 end
223 end
224 end
225 end
226
227 %% SYMMETRY: p2(i,ni,hi,j,nj,kj) = p2(j,nj,kj,i,ni,hi)
228 if verbose; fprintf(' SYMMETRY constraints...\n'); end
229 for j = 1:M
230 for nj = 0:F(j)
231 for kj = 1:K(j)
232 for i = 1:M
233 if i <= j
234 continue; % avoid duplicate constraints
235 end
236 for ni = 0:F(i)
237 for hi = 1:K(i)
238 idx1 = getIdx(j, nj, kj, i, ni, hi);
239 idx2 = getIdx(i, ni, hi, j, nj, kj);
240 if idx1 ~= idx2
241 row = zeros(1, nVars);
242 row(idx1) = 1;
243 row(idx2) = -1;
244 Aeq = [Aeq; row];
245 beq = [beq; 0];
246 end
247 end
248 end
249 end
250 end
251 end
252 end
253
254 %% MARGINALS: p2(j,nj,kj,j,nj,kj) = sum over ni,hi of p2(j,nj,kj,i,ni,hi)
255 if verbose; fprintf(' MARGINALS constraints...\n'); end
256 for j = 1:M
257 for kj = 1:K(j)
258 for nj = 0:F(j)
259 for i = 1:M
260 if i == j
261 continue;
262 end
263 row = zeros(1, nVars);
264 idx_diag = getIdx(j, nj, kj, j, nj, kj);
265 row(idx_diag) = 1;
266 for ni = 0:F(i)
267 for hi = 1:K(i)
268 idx = getIdx(j, nj, kj, i, ni, hi);
269 row(idx) = row(idx) - 1;
270 end
271 end
272 Aeq = [Aeq; row];
273 beq = [beq; 0];
274 end
275 end
276 end
277 end
278
279 %% UCLASSIC: U(i,k,ni) = p2(i,ni,k,i,ni,k)
280 if verbose; fprintf(' UCLASSIC constraints...\n'); end
281 for i = 1:M
282 for ki = 1:K(i)
283 for ni = 1:F(i)
284 row = zeros(1, nVars);
285 idx_U = getUIdx(i, ki, ni);
286 idx_p2 = getIdx(i, ni, ki, i, ni, ki);
287 row(idx_U) = 1;
288 row(idx_p2) = -1;
289 Aeq = [Aeq; row];
290 beq = [beq; 0];
291 end
292 end
293 end
294
295 %% UEFFS: Ueff(i,k,ni) = p2(i,ni,k,i,ni,k) - sum_{j: r(i,j)>0} r(i,j)*p2(i,ni,k,j,F(j),h)
296 if verbose; fprintf(' UEFFS constraints...\n'); end
297 for i = 1:M
298 for ki = 1:K(i)
299 for ni = 1:F(i)
300 row = zeros(1, nVars);
301 idx_Ueff = getUeffIdx(i, ki, ni);
302 idx_p2_diag = getIdx(i, ni, ki, i, ni, ki);
303 row(idx_Ueff) = 1;
304 row(idx_p2_diag) = -1;
305 % Add blocking terms
306 for j = 1:M
307 if j ~= i && r(i,j) > 0
308 for hj = 1:K(j)
309 idx_block = getIdx(i, ni, ki, j, F(j), hj);
310 row(idx_block) = r(i,j);
311 end
312 end
313 end
314 Aeq = [Aeq; row];
315 beq = [beq; 0];
316 end
317 end
318 end
319
320 %% PBLOCK: pb(i) = sum_{k,ni>=1} (U(i,k,ni) - Ueff(i,k,ni))
321 if verbose; fprintf(' PBLOCK constraints...\n'); end
322 for i = 1:M
323 row = zeros(1, nVars);
324 row(pbidx(i)) = 1;
325 for ki = 1:K(i)
326 for ni = 1:F(i)
327 row(getUIdx(i, ki, ni)) = row(getUIdx(i, ki, ni)) - 1;
328 row(getUeffIdx(i, ki, ni)) = row(getUeffIdx(i, ki, ni)) + 1;
329 end
330 end
331 Aeq = [Aeq; row];
332 beq = [beq; 0];
333 end
334
335 %% PBB: pb(i) <= sum_{j<>i, r(i,j)>0} sum_h p2(j,F(j),h,j,F(j),h)
336 if verbose; fprintf(' PBB constraints...\n'); end
337 for i = 1:M
338 row = zeros(1, nVars);
339 row(pbidx(i)) = 1;
340 for j = 1:M
341 if j ~= i && r(i,j) > 0
342 for hj = 1:K(j)
343 idx = getIdx(j, F(j), hj, j, F(j), hj);
344 row(idx) = row(idx) - 1;
345 end
346 end
347 end
348 Aineq = [Aineq; row];
349 bineq = [bineq; 0];
350 end
351
352 %% THM2: Phase balance (Theorem 1 - THM:sdeffective)
353 % sum_{ni} (sum_{j<>i, h<>k} q(i,j,k,h,ni)*Ueff(i,k,ni) + sum_{h<>k} q(i,i,k,h,ni)*U(i,k,ni))
354 % = sum_{ni} (sum_{j<>i, h<>k} q(i,j,h,k,ni)*Ueff(i,h,ni) + sum_{h<>k} q(i,i,h,k,ni)*U(i,h,ni))
355 if verbose; fprintf(' THM2 (Phase balance) constraints...\n'); end
356 for i = 1:M
357 for ki = 1:K(i)
358 row = zeros(1, nVars);
359 % LHS terms
360 for ni = 1:F(i)
361 % Terms with Ueff (j<>i)
362 for j = 1:M
363 if j == i
364 continue;
365 end
366 for hi = 1:K(i)
367 if hi == ki
368 continue;
369 end
370 coef = q{i,j}(ki, hi, ni+1);
371 idx_Ueff = getUeffIdx(i, ki, ni);
372 row(idx_Ueff) = row(idx_Ueff) + coef;
373 end
374 end
375 % Terms with U (j==i, h<>k)
376 for hi = 1:K(i)
377 if hi == ki
378 continue;
379 end
380 coef = q{i,i}(ki, hi, ni+1);
381 idx_p2 = getIdx(i, ni, ki, i, ni, ki);
382 row(idx_p2) = row(idx_p2) + coef;
383 end
384 end
385 % RHS terms (subtract)
386 for ni = 1:F(i)
387 % Terms with Ueff (j<>i)
388 for j = 1:M
389 if j == i
390 continue;
391 end
392 for hi = 1:K(i)
393 if hi == ki
394 continue;
395 end
396 coef = q{i,j}(hi, ki, ni+1);
397 idx_Ueff = getUeffIdx(i, hi, ni);
398 row(idx_Ueff) = row(idx_Ueff) - coef;
399 end
400 end
401 % Terms with U (j==i, h<>k)
402 for hi = 1:K(i)
403 if hi == ki
404 continue;
405 end
406 coef = q{i,i}(hi, ki, ni+1);
407 idx_p2 = getIdx(i, ni, hi, i, ni, hi);
408 row(idx_p2) = row(idx_p2) - coef;
409 end
410 end
411 Aeq = [Aeq; row];
412 beq = [beq; 0];
413 end
414 end
415
416 %% THM1: Population constraint (Theorem 2)
417 % sum_i sum_ni sum_hi ni * p2(j,nj,kj,i,ni,hi) = N * p2(j,nj,kj,j,nj,kj)
418 if verbose; fprintf(' THM1 (Population) constraints...\n'); end
419 % THM1 is AGGREGATED over nj >= 1: one row per (j,kj), matching the
420 % authoritative AMPL model (qrboundsrsrd_skel.mod / example_rsrd.mod):
421 % THM1 {j,k}: sum_i sum_{nj>=1} sum_{ni>=1} sum_h ni*p2[j,nj,k,i,ni,h]
422 % = N * sum_{nj>=1} p2[j,nj,k,j,nj,k]
423 % Emitting this per-nj instead (one row for each nj) is STRICTLY STRONGER --
424 % the per-nj equalities imply the aggregate but not conversely -- and it
425 % over-tightens the polytope: on the paper's M=5,N=20 RS-RD example the
426 % per-nj form gives U1min=0.92508 against the published 0.87058.
427 for j = 1:M
428 for kj = 1:K(j)
429 row = zeros(1, nVars);
430 for nj = 1:F(j)
431 idx_diag = getIdx(j, nj, kj, j, nj, kj);
432 row(idx_diag) = row(idx_diag) - N;
433 for i = 1:M
434 for ni = 1:F(i) % ni >= 1
435 for hi = 1:K(i)
436 idx = getIdx(j, nj, kj, i, ni, hi);
437 row(idx) = row(idx) + ni;
438 end
439 end
440 end
441 end
442 Aeq = [Aeq; row];
443 beq = [beq; 0];
444 end
445 end
446
447 %% THM1c: population balance for the empty-queue case nj = 0 (i ~= j)
448 % THM1c {j,k}: sum_{i<>j} sum_{ni>=1} sum_h ni*p2[j,0,k,i,ni,h]
449 % = N * p2[j,0,k,j,0,k]
450 if verbose; fprintf(' THM1c (nj=0 population) constraints...\n'); end
451 for j = 1:M
452 for kj = 1:K(j)
453 row = zeros(1, nVars);
454 idx_diag = getIdx(j, 0, kj, j, 0, kj);
455 row(idx_diag) = row(idx_diag) - N;
456 for i = 1:M
457 if i == j
458 continue;
459 end
460 for ni = 1:F(i) % ni >= 1
461 for hi = 1:K(i)
462 idx = getIdx(j, 0, kj, i, ni, hi);
463 row(idx) = row(idx) + ni;
464 end
465 end
466 end
467 Aeq = [Aeq; row];
468 beq = [beq; 0];
469 end
470 end
471
472 %% THM3a: Marginal balance for ni in 1:F(i)-1
473 if verbose; fprintf(' THM3a (Marginal balance) constraints...\n'); end
474 for i = 1:M
475 for ni = 1:(F(i)-1)
476 row = zeros(1, nVars);
477 % LHS: arrivals to queue i
478 for j = 1:M
479 if j == i
480 continue;
481 end
482 for kj = 1:K(j)
483 for hj = 1:K(j)
484 for ui = 1:K(i)
485 for nj = 1:F(j)
486 idx = getIdx(j, nj, kj, i, ni, ui);
487 row(idx) = row(idx) + q{j,i}(kj, hj, nj+1);
488 end
489 end
490 end
491 end
492 end
493 % RHS: departures from queue i at ni+1
494 for j = 1:M
495 if j == i
496 continue;
497 end
498 for ki = 1:K(i)
499 for hi = 1:K(i)
500 for uj = 1:K(j)
501 for nj = 0:(F(j)-1)
502 idx = getIdx(i, ni+1, ki, j, nj, uj);
503 row(idx) = row(idx) - q{i,j}(ki, hi, ni+2);
504 end
505 end
506 end
507 end
508 end
509 Aeq = [Aeq; row];
510 beq = [beq; 0];
511 end
512 end
513
514 %% THM3b: Marginal balance for ni=0, per phase
515 if verbose; fprintf(' THM3b (Marginal balance ni=0) constraints...\n'); end
516 for i = 1:M
517 for ui = 1:K(i)
518 row = zeros(1, nVars);
519 % LHS: arrivals to queue i at ni=0
520 for j = 1:M
521 if j == i
522 continue;
523 end
524 for kj = 1:K(j)
525 for hj = 1:K(j)
526 for nj = 1:F(j)
527 idx = getIdx(j, nj, kj, i, 0, ui);
528 row(idx) = row(idx) + q{j,i}(kj, hj, nj+1);
529 end
530 end
531 end
532 end
533 % RHS: departures from queue i at ni=1
534 for j = 1:M
535 if j == i
536 continue;
537 end
538 for ki = 1:K(i)
539 for nj = 0:(F(j)-1)
540 for hj = 1:K(j)
541 idx = getIdx(i, 1, ki, j, nj, hj);
542 row(idx) = row(idx) - q{i,j}(ki, ui, 2);
543 end
544 end
545 end
546 end
547 Aeq = [Aeq; row];
548 beq = [beq; 0];
549 end
550 end
551
552 %% QBAL: Queue balance constraint
553 % This is a complex balance equation that tightens the bounds
554 if verbose; fprintf(' QBAL (Queue balance) constraints...\n'); end
555 for i = 1:M
556 for ki = 1:K(i)
557 row = zeros(1, nVars);
558
559 % LHS Term 1: sum{h<>k} sum{j<>i} sum{ni} sum{u} sum{nj} q[i,j,k,h,ni]*ni*p2[i,ni,k,j,nj,u]
560 for hi = 1:K(i)
561 if hi == ki
562 continue;
563 end
564 for j = 1:M
565 if j == i
566 continue;
567 end
568 for ni = 1:F(i)
569 for uj = 1:K(j)
570 for nj = 0:(F(j)-1)
571 coef = q{i,j}(ki, hi, ni+1) * ni;
572 idx = getIdx(i, ni, ki, j, nj, uj);
573 row(idx) = row(idx) + coef;
574 end
575 end
576 end
577 end
578 end
579
580 % LHS Term 2: sum{h<>k} sum{ni} q[i,i,k,h,ni]*ni*p2[i,ni,k,i,ni,k]
581 for hi = 1:K(i)
582 if hi == ki
583 continue;
584 end
585 for ni = 1:F(i)
586 coef = q{i,i}(ki, hi, ni+1) * ni;
587 idx = getIdx(i, ni, ki, i, ni, ki);
588 row(idx) = row(idx) + coef;
589 end
590 end
591
592 % LHS Term 3: sum{j<>i} sum{h} sum{ni} sum{u} sum{nj<=min(F(j)-1,N-ni)} q[i,j,h,k,ni]*p2[i,ni,h,j,nj,u]
593 for j = 1:M
594 if j == i
595 continue;
596 end
597 for hi = 1:K(i)
598 for ni = 1:F(i)
599 for uj = 1:K(j)
600 maxNj = min(F(j)-1, N-ni);
601 for nj = 0:maxNj
602 coef = q{i,j}(hi, ki, ni+1);
603 idx = getIdx(i, ni, hi, j, nj, uj);
604 row(idx) = row(idx) + coef;
605 end
606 end
607 end
608 end
609 end
610
611 % RHS Term 1: sum{j<>i} sum{h} sum{ni<=F(i)-1} sum{u} sum{nj>=1} q[j,i,h,u,nj]*p2[i,ni,k,j,nj,h]
612 for j = 1:M
613 if j == i
614 continue;
615 end
616 for hj = 1:K(j)
617 for ni = 0:(F(i)-1)
618 for uj = 1:K(j)
619 for nj = 1:F(j)
620 coef = q{j,i}(hj, uj, nj+1);
621 idx = getIdx(i, ni, ki, j, nj, hj);
622 row(idx) = row(idx) - coef;
623 end
624 end
625 end
626 end
627 end
628
629 % RHS Term 2: sum{h<>k} sum{ni} q[i,i,h,k,ni]*ni*p2[i,ni,h,i,ni,h]
630 for hi = 1:K(i)
631 if hi == ki
632 continue;
633 end
634 for ni = 1:F(i)
635 coef = q{i,i}(hi, ki, ni+1) * ni;
636 idx = getIdx(i, ni, hi, i, ni, hi);
637 row(idx) = row(idx) - coef;
638 end
639 end
640
641 % RHS Term 3: sum{h<>k} sum{j<>i} sum{ni} sum{u} sum{nj} q[i,j,h,k,ni]*ni*p2[i,ni,h,j,nj,u]
642 for hi = 1:K(i)
643 if hi == ki
644 continue;
645 end
646 for j = 1:M
647 if j == i
648 continue;
649 end
650 for ni = 1:F(i)
651 for uj = 1:K(j)
652 for nj = 0:(F(j)-1)
653 coef = q{i,j}(hi, ki, ni+1) * ni;
654 idx = getIdx(i, ni, hi, j, nj, uj);
655 row(idx) = row(idx) - coef;
656 end
657 end
658 end
659 end
660 end
661
662 Aeq = [Aeq; row];
663 beq = [beq; 0];
664 end
665 end
666
667 %% THM4: Queue-length bound inequality (Theorem 5)
668 % sum_t sum_ht sum_nj sum_nt nt*p2(j,nj,kj,t,nt,ht) >= N * sum_hi sum_nj sum_ni p2(j,nj,kj,i,ni,hi)
669 if verbose; fprintf(' THM4 (Queue-length bound) constraints...\n'); end
670 for j = 1:M
671 for kj = 1:K(j)
672 for i = 1:M
673 row = zeros(1, nVars);
674 % LHS: sum_t sum_ht sum_nj sum_nt nt * p2
675 for t = 1:M
676 for ht = 1:K(t)
677 for nj = 0:F(j)
678 for nt = 1:F(t) % nt >= 1
679 idx = getIdx(j, nj, kj, t, nt, ht);
680 row(idx) = row(idx) + nt;
681 end
682 end
683 end
684 end
685 % RHS: -N * sum
686 for hi = 1:K(i)
687 for nj = 0:F(j)
688 for ni = 1:F(i) % ni >= 1
689 idx = getIdx(j, nj, kj, i, ni, hi);
690 row(idx) = row(idx) - N;
691 end
692 end
693 end
694 Aineq = [Aineq; -row]; % >= becomes <= with negation
695 bineq = [bineq; 0];
696 end
697 end
698 end
699
700 %% Build objective function
701 if verbose; fprintf('Building objective function...\n'); end
702 c = zeros(nVars, 1);
703
704 if ischar(objective)
705 if strcmp(objective, 'U1min') || strcmp(objective, 'U1max')
706 targetQueue = 1;
707 else
708 error('Unknown objective: %s', objective);
709 end
710 else
711 targetQueue = objective;
712 end
713
714 % Utilization = sum over k, n of p2(i,n,k,i,n,k)
715 for ki = 1:K(targetQueue)
716 for ni = 1:F(targetQueue)
717 idx = getIdx(targetQueue, ni, ki, targetQueue, ni, ki);
718 c(idx) = 1;
719 end
720 end
721
722 if strcmp(sense, 'max')
723 c = -c;
724 end
725
726 %% Solve LP
727 if verbose; fprintf('Solving LP with %d variables and %d equality + %d inequality constraints...\n', ...
728 nVars, size(Aeq, 1), size(Aineq, 1)); end
729
730 % LP algorithm. R2025a's default 'dual-simplex-highs' is broken in some
731 % installs (errors "Unrecognized field name optimstatus"), so an
732 % interior-point variant is required. On the badly-scaled QRF instances
733 % 'interior-point-legacy' is markedly more accurate than 'interior-point';
734 % on the paper's BAS model it cuts the error against the published GLPK
735 % optimum from 4.3e-05/8.0e-06 to 8.5e-07/9.2e-10. The residual is solver
736 % tolerance, not formulation: the minimum lands above and the maximum below
737 % the GLPK vertex, and both gaps shrink together as accuracy rises.
738 % Override via params.lpAlgorithm.
739 if isfield(params, 'lpAlgorithm') && ~isempty(params.lpAlgorithm)
740 lpAlgorithm = params.lpAlgorithm;
741 else
742 lpAlgorithm = 'interior-point-legacy';
743 end
744 if verbose
745 options = optimoptions('linprog', 'Display', 'final', 'Algorithm', lpAlgorithm);
746 else
747 options = optimoptions('linprog', 'Display', 'off', 'Algorithm', lpAlgorithm);
748 end
749
750 [x, fval, exitflag] = linprog(c, Aineq, bineq, Aeq, beq, lb, ub, options);
751
752 if strcmp(sense, 'max')
753 fval = -fval;
754 end
755
756 %% Extract results
757 result = struct();
758 result.objective = fval;
759 result.exitflag = exitflag;
760
761 % Whether the metric fields below can be filled is a property of the
762 % solution vector, not of exitflag. exitflag 0 (iteration limit) still
763 % returns a usable interior point -- that is the normal outcome on badly
764 % scaled instances such as the reference BAS network -- whereas a solve that
765 % breaks down returns an empty or non-finite x, which must never be
766 % consumed. Predicate on x accordingly, and say so rather than skipping
767 % silently. (Observed here: exitflag -4 comes back with x empty.)
768 hasSolution = ~isempty(x) && all(isfinite(x));
769 if ~isempty(x) && ~hasSolution
770 warning('qrf_rsrd:nonFiniteSolution', ...
771 'linprog returned a non-finite solution (exitflag %d); U and the other metric fields are left unpopulated.', ...
772 exitflag);
773 end
774
775 if hasSolution
776 % Compute utilizations from U and Ueff variables
777 result.U = zeros(M, 1);
778 result.Ueff = zeros(M, 1);
779 result.pb = zeros(M, 1);
780
781 for i = 1:M
782 % U(i) = sum over k, ni of U(i,k,ni)
783 for ki = 1:K(i)
784 for ni = 1:F(i)
785 idx_U = getUIdx(i, ki, ni);
786 idx_Ueff = getUeffIdx(i, ki, ni);
787 result.U(i) = result.U(i) + x(idx_U);
788 result.Ueff(i) = result.Ueff(i) + x(idx_Ueff);
789 end
790 end
791 result.pb(i) = result.U(i) - result.Ueff(i);
792 end
793
794 % Store p2 as a function handle for easy access
795 result.getP2 = @(j, nj, kj, i, ni, hi) x(getIdx(j, nj, kj, i, ni, hi));
796 end
797
798 if verbose; fprintf('\n=== Results ===\n'); end
799 if verbose; fprintf('Objective value: %f\n', fval); end
800 if verbose; fprintf('Exit flag: %d\n', exitflag); end
801 if hasSolution && verbose
802 fprintf('\nUtilizations:\n');
803 for i = 1:M
804 fprintf(' Queue %d: U = %.6f, Ueff = %.6f, pb = %.6f\n', ...
805 i, result.U(i), result.Ueff(i), result.pb(i));
806 end
807 end
808end
Definition Station.m:287
Definition Station.m:245