1function [result, x, fval, exitflag] = qrf_bas(params, objective, sense)
2% QRF_BAS - Quadratic Reduction Framework
for BAS (Blocking-After-Service) networks
4% MATLAB port of
the AMPL model qrboundsbas_skel.mod
7% [result, x, fval, exitflag] = qrf_bas(params)
8% [result, x, fval, exitflag] = qrf_bas(params, objective)
9% [result, x, fval, exitflag] = qrf_bas(params, objective, sense)
12% params - Structure with model parameters:
13% .M - Number of queues
14% .N - Total population
15% .f - Index of finite capacity queue (1-based)
16% .F - [M x 1] Capacity of each queue
17% .K - [M x 1] Number of phases
for each queue
18% .mu - {M x 1} cell, each mu{i}
is K(i) x K(i) completion rates
19% .v - {M x 1} cell, each v{i}
is K(i) x K(i) background rates
20% .r - [M x M] Routing probabilities
21% .MR - Number of blocking configurations
22% .BB - [MR x M] Blocking state (0/1)
23% .MM - [MR x 2] Blocking order (queue indices)
24% .ZZ - [MR x 1] Number of blocked queues in each config
25% .ZM - Maximum blocking depth
26% .MM1 - [MR x M] Extended blocking order info
28% .lpAlgorithm - (optional) linprog Algorithm
string;
29% defaults to
'interior-point-legacy' (see note at
the
30% LP solve
for why, and
for the R2025a HiGHS caveat)
32% objective - (optional)
'U1min' (
default),
'U1max', or queue index 1..M
33% sense - (optional)
'min' (
default) or
'max'
36% result - Structure with results:
37% .U - [M x 1] Utilization of each queue
38% .e - [M x max(K)] Effective utilization by phase
39% x - Raw solution vector
40% fval - Objective function value
41% exitflag - Solver exit flag
43 if nargin < 2 || isempty(objective)
46 if nargin < 3 || isempty(sense)
53 f = params.f; % finite capacity queue index
65 if isfield(params,
'verbose')
66 verbose = params.verbose;
71 % Compute transition rates q(i,j,k,h)
72 % q{i,j}
is a K(i) x K(i) array (not load-dependent
for BAS)
76 q{i,j} = zeros(K(i), K(i));
80 q{i,j}(ki, hi) = r(i,j) * mu{i}(ki, hi);
82 q{i,j}(ki, hi) = v{i}(ki, hi) + r(i,i) * mu{i}(ki, hi);
89 %% Build variable indexing
90 % p2(j, nj, kj, i, ni, hi, m)
for j in 1:M, nj in 0:N, kj in 1:K(j),
91 % i in 1:M, ni in 0:N, hi in 1:K(i), m in 1:MR
92 % e(i, ki)
for i in 1:M, ki in 1:K(i)
94 if verbose; fprintf(
'Building variable index map...\n'); end
100 p2idx{j} = cell(N+1, K(j), M, MR);
105 p2idx{j}{nj+1, kj, i, m} = zeros(N+1, K(i));
108 varCount = varCount + 1;
109 p2idx{j}{nj+1, kj, i, m}(ni+1, hi) = varCount;
121 eidx{i} = zeros(K(i), 1);
123 varCount = varCount + 1;
124 eidx{i}(ki) = varCount;
129 if verbose; fprintf(
'Total variables: %d\n', nVars); end
132 % Sparse triplet accumulation. The previous builder used
133 % row = zeros(1,nVars); row(idx) = row(idx) + v; Aeq = [Aeq; row];
134 % which
is O(nrows^2) in memory traffic: every append reallocated and copied
135 %
the whole matrix, and every row allocated nVars doubles. For
the BAS
136 % example (nVars ~6e4, ~1.1e5 rows) that does not terminate in practice.
137 % We emit (row,col,val) triplets and build one sparse matrix at
the end.
138 % sparse() SUMS duplicate (i,j) entries, which reproduces
the
139 % row(idx)=row(idx)+v accumulation exactly. The plain assignments below
140 % (SYMMETRY +-1, UEFF row(idx_e)=-1, THM2 row(idx_diag)=-N, ONE/MARGINALS
141 % =1) are each
the FIRST write to their index within their row, so emitting
142 % them as triplets
is equivalent; SYMMETRY additionally guards idx1~=idx2.
144 eqI = zeros(eqNnzCap, 1); eqJ = zeros(eqNnzCap, 1); eqV = zeros(eqNnzCap, 1);
147 inI = zeros(inNnzCap, 1); inJ = zeros(inNnzCap, 1); inV = zeros(inNnzCap, 1);
149 beq = zeros(65536, 1);
150 bineq = zeros(4096, 1);
151 rCap = 4096; rI = zeros(rCap, 1); rV = zeros(rCap, 1); rN = 0;
154 getP2Idx = @(j, nj, kj, i, ni, hi, m) p2idx{j}{nj+1, kj, i, m}(ni+1, hi);
155 getEIdx = @(i, ki) eidx{i}(ki);
157 if verbose; fprintf(
'Building constraints...\n'); end
160 lb = zeros(nVars, 1);
163 %% ZERO constraints - fix infeasible states
164 if verbose; fprintf(
' ZERO constraints...\n'); end
172 idx = getP2Idx(j, nj, kj, i, ni, hi, m);
174 % ZERO1: i==j, nj==ni, h<>k
175 if i == j && nj == ni && hi ~= kj
179 % ZERO2: i==j, nj<>ni
180 if i == j && nj ~= ni
184 % ZERO3: i<>j, nj+ni > N
185 if i ~= j && nj + ni > N
194 % ZERO5: BB(m,j)==1 and nj==0
195 if m >= 2 && BB(m, j) == 1 && nj == 0
199 % ZERO7: BB(m,j)==1 and i<>j and i<>f and ni+nj+F(f)>N
200 if m >= 2 && BB(m, j) == 1 && i ~= j && i ~= f && ni + nj + F(f) > N
204 % ZERO8: finite queue not at capacity in blocking config
205 if j == f && nj >= 1 && nj <= F(f)-1 && m >= 2
216 % ZERO4: For m>=2 and j<>f, p2(j,nj,k,f,nf,h,m)=0 when nf < F(f)
226 idx = getP2Idx(j, nj, kj, f, nf, hf, m);
235 %% ONE: Normalization
236 if verbose; fprintf(
' ONE constraints...\n'); end
242 idx = getP2Idx(j, nj, kj, j, nj, kj, m);
243 rN=rN+1;
if rN>rCap, rCap=2*rCap; rI(rCap)=0; rV(rCap)=0; end; rI(rN)=idx; rV(rN)=1;
247 nEq=nEq+1;
if eqNnz+rN>numel(eqI), eqI(2*(eqNnz+rN))=0; eqJ(2*(eqNnz+rN))=0; eqV(2*(eqNnz+rN))=0; end; eqI(eqNnz+(1:rN))=nEq; eqJ(eqNnz+(1:rN))=rI(1:rN); eqV(eqNnz+(1:rN))=rV(1:rN); eqNnz=eqNnz+rN;
if nEq>numel(beq), beq(2*nEq)=0; end
252 if verbose; fprintf(
' SYMMETRY constraints...\n'); end
254 for nj = 0:min(N, F(j))
260 for ni = 0:min(N, F(i))
261 if i ~= j && nj + ni > N
266 idx1 = getP2Idx(j, nj, kj, i, ni, hi, m);
267 idx2 = getP2Idx(i, ni, hi, j, nj, kj, m);
268 if ub(idx1) == 0 && ub(idx2) == 0
273 rN=rN+1;
if rN>rCap, rCap=2*rCap; rI(rCap)=0; rV(rCap)=0; end; rI(rN)=idx1; rV(rN)=1;
274 rN=rN+1;
if rN>rCap, rCap=2*rCap; rI(rCap)=0; rV(rCap)=0; end; rI(rN)=idx2; rV(rN)=-1;
275 nEq=nEq+1;
if eqNnz+rN>numel(eqI), eqI(2*(eqNnz+rN))=0; eqJ(2*(eqNnz+rN))=0; eqV(2*(eqNnz+rN))=0; end; eqI(eqNnz+(1:rN))=nEq; eqJ(eqNnz+(1:rN))=rI(1:rN); eqV(eqNnz+(1:rN))=rV(1:rN); eqNnz=eqNnz+rN;
if nEq>numel(beq), beq(2*nEq)=0; end
287 if verbose; fprintf(
' MARGINALS constraints...\n'); end
290 for nj = 0:min(N, F(j))
297 idx_diag = getP2Idx(j, nj, kj, j, nj, kj, m);
298 rN=rN+1;
if rN>rCap, rCap=2*rCap; rI(rCap)=0; rV(rCap)=0; end; rI(rN)=idx_diag; rV(rN)=1;
299 for ni = 0:min(N-nj, F(i))
301 idx = getP2Idx(j, nj, kj, i, ni, hi, m);
302 rN=rN+1;
if rN>rCap, rCap=2*rCap; rI(rCap)=0; rV(rCap)=0; end; rI(rN)=idx; rV(rN)=-(1);
305 nEq=nEq+1;
if eqNnz+rN>numel(eqI), eqI(2*(eqNnz+rN))=0; eqJ(2*(eqNnz+rN))=0; eqV(2*(eqNnz+rN))=0; end; eqI(eqNnz+(1:rN))=nEq; eqJ(eqNnz+(1:rN))=rI(1:rN); eqV(eqNnz+(1:rN))=rV(1:rN); eqNnz=eqNnz+rN;
if nEq>numel(beq), beq(2*nEq)=0; end
313 %% UEFF: e(i,ki) = sum of p2 where queue i
is not blocked
314 if verbose; fprintf(
' UEFF constraints...\n'); end
318 idx_e = getEIdx(i, ki);
319 rN=rN+1;
if rN>rCap, rCap=2*rCap; rI(rCap)=0; rV(rCap)=0; end; rI(rN)=idx_e; rV(rN)=-1;
321 for nj = 0:min(N, F(j))
325 for ni = 1:min(N, F(i))
326 idx = getP2Idx(j, nj, kj, i, ni, ki, m);
327 rN=rN+1;
if rN>rCap, rCap=2*rCap; rI(rCap)=0; rV(rCap)=0; end; rI(rN)=idx; rV(rN)=1;
334 nEq=nEq+1;
if eqNnz+rN>numel(eqI), eqI(2*(eqNnz+rN))=0; eqJ(2*(eqNnz+rN))=0; eqV(2*(eqNnz+rN))=0; end; eqI(eqNnz+(1:rN))=nEq; eqJ(eqNnz+(1:rN))=rI(1:rN); eqV(eqNnz+(1:rN))=rV(1:rN); eqNnz=eqNnz+rN;
if nEq>numel(beq), beq(2*nEq)=0; end
339 %% THM1: Phase balance (Theorem 1)
340 % sum {j, h: j<>i or h<>k} q(i,j,k,h)*e(i,k) = sum {j, h: j<>i or h<>k} q(i,j,h,k)*e(i,h)
341 if verbose; fprintf(
' THM1 (Phase balance) constraints...\n'); end
348 if j ~= i || hi ~= ki
349 idx_e = getEIdx(i, ki);
350 rN=rN+1;
if rN>rCap, rCap=2*rCap; rI(rCap)=0; rV(rCap)=0; end; rI(rN)=idx_e; rV(rN)=q{i,j}(ki, hi);
357 if j ~= i || hi ~= ki
358 idx_e = getEIdx(i, hi);
359 rN=rN+1;
if rN>rCap, rCap=2*rCap; rI(rCap)=0; rV(rCap)=0; end; rI(rN)=idx_e; rV(rN)=-(q{i,j}(hi, ki));
363 nEq=nEq+1;
if eqNnz+rN>numel(eqI), eqI(2*(eqNnz+rN))=0; eqJ(2*(eqNnz+rN))=0; eqV(2*(eqNnz+rN))=0; end; eqI(eqNnz+(1:rN))=nEq; eqJ(eqNnz+(1:rN))=rI(1:rN); eqV(eqNnz+(1:rN))=rV(1:rN); eqNnz=eqNnz+rN;
if nEq>numel(beq), beq(2*nEq)=0; end
368 %% THM2: Population constraint (Theorem 2)
369 if verbose; fprintf(
' THM2 (Population) constraints...\n'); end
375 % RHS: -N * p2(j,nj,kj,j,nj,kj,m)
376 idx_diag = getP2Idx(j, nj, kj, j, nj, kj, m);
377 rN=rN+1;
if rN>rCap, rCap=2*rCap; rI(rCap)=0; rV(rCap)=0; end; rI(rN)=idx_diag; rV(rN)=-N;
382 idx = getP2Idx(j, nj, kj, i, ni, ki, m);
383 rN=rN+1;
if rN>rCap, rCap=2*rCap; rI(rCap)=0; rV(rCap)=0; end; rI(rN)=idx; rV(rN)=ni;
387 nEq=nEq+1;
if eqNnz+rN>numel(eqI), eqI(2*(eqNnz+rN))=0; eqJ(2*(eqNnz+rN))=0; eqV(2*(eqNnz+rN))=0; end; eqI(eqNnz+(1:rN))=nEq; eqJ(eqNnz+(1:rN))=rI(1:rN); eqV(eqNnz+(1:rN))=rV(1:rN); eqNnz=eqNnz+rN;
if nEq>numel(beq), beq(2*nEq)=0; end
394 %% COR1: Second moment constraint (Corollary to Theorem 2)
395 % sum_{m,i,j,nj,ni,ki,kj} ni*nj*p2(j,nj,kj,i,ni,ki,m) = N^2
396 if verbose; fprintf(
' COR1 (Second moment) constraint...\n'); end
405 idx = getP2Idx(j, nj, kj, i, ni, ki, m);
406 rN=rN+1;
if rN>rCap, rCap=2*rCap; rI(rCap)=0; rV(rCap)=0; end; rI(rN)=idx; rV(rN)=ni * nj;
414 nEq=nEq+1;
if eqNnz+rN>numel(eqI), eqI(2*(eqNnz+rN))=0; eqJ(2*(eqNnz+rN))=0; eqV(2*(eqNnz+rN))=0; end; eqI(eqNnz+(1:rN))=nEq; eqJ(eqNnz+(1:rN))=rI(1:rN); eqV(eqNnz+(1:rN))=rV(1:rN); eqNnz=eqNnz+rN;
if nEq>numel(beq), beq(2*nEq)=0; end
417 %% THM30: Marginal balance
for ni=0 (per phase), i<>f
418 if verbose; fprintf(
' THM30 (Marginal balance ni=0) constraints...\n'); end
425 % LHS: arrivals from j<>i,j<>f with BB(m,j)==0
435 idx = getP2Idx(j, nj, kj, i, 0, ui, m);
436 rN=rN+1;
if rN>rCap, rCap=2*rCap; rI(rCap)=0; rV(rCap)=0; end; rI(rN)=idx; rV(rN)=q{j,i}(kj, hj);
443 % LHS: arrivals from j==f with MM(m,1)<>i
449 idx = getP2Idx(f, nj, kj, i, 0, ui, m);
450 rN=rN+1;
if rN>rCap, rCap=2*rCap; rI(rCap)=0; rV(rCap)=0; end; rI(rN)=idx; rV(rN)=q{f,i}(kj, hj);
457 % RHS: departures from i at ni=1 to j<>i,j<>f with BB(m,i)==0
467 idx = getP2Idx(j, nj, hj, i, 1, ki, m);
468 rN=rN+1;
if rN>rCap, rCap=2*rCap; rI(rCap)=0; rV(rCap)=0; end; rI(rN)=idx; rV(rN)=-(q{i,j}(ki, ui));
475 % RHS: departures to j==f with BB(m,i)==0 and nj<F(f)
481 idx = getP2Idx(f, nj, hj, i, 1, ki, m);
482 rN=rN+1;
if rN>rCap, rCap=2*rCap; rI(rCap)=0; rV(rCap)=0; end; rI(rN)=idx; rV(rN)=-(q{i,f}(ki, ui));
488 % RHS: unblocking when BB(m,i)==1 and MM(m,1)==i
490 if BB(m, i) == 1 && MM(m, 1) == i
495 idx = getP2Idx(f, F(f), kf, i, 1, ui, m);
496 rN=rN+1;
if rN>rCap, rCap=2*rCap; rI(rCap)=0; rV(rCap)=0; end; rI(rN)=idx; rV(rN)=-(q{f,w}(kf, pf));
504 nEq=nEq+1;
if eqNnz+rN>numel(eqI), eqI(2*(eqNnz+rN))=0; eqJ(2*(eqNnz+rN))=0; eqV(2*(eqNnz+rN))=0; end; eqI(eqNnz+(1:rN))=nEq; eqJ(eqNnz+(1:rN))=rI(1:rN); eqV(eqNnz+(1:rN))=rV(1:rN); eqNnz=eqNnz+rN;
if nEq>numel(beq), beq(2*nEq)=0; end
509 %% THM3: Marginal balance
for ni in 1:F(i)-1, i<>f
510 if verbose; fprintf(
' THM3 (Marginal balance) constraints...\n'); end
517 % LHS: arrivals from j<>i,j<>f with BB(m,j)==0
528 idx = getP2Idx(j, nj, kj, i, ni, ui, m);
529 rN=rN+1;
if rN>rCap, rCap=2*rCap; rI(rCap)=0; rV(rCap)=0; end; rI(rN)=idx; rV(rN)=q{j,i}(kj, hj);
537 % LHS: arrivals from j==f with MM(m,1)<>i
544 idx = getP2Idx(f, nj, kj, i, ni, ui, m);
545 rN=rN+1;
if rN>rCap, rCap=2*rCap; rI(rCap)=0; rV(rCap)=0; end; rI(rN)=idx; rV(rN)=q{f,i}(kj, hj);
553 % RHS: departures from i at ni+1 to j<>i,j<>f with BB(m,i)==0
564 idx = getP2Idx(j, nj, uj, i, ni+1, ki, m);
565 rN=rN+1;
if rN>rCap, rCap=2*rCap; rI(rCap)=0; rV(rCap)=0; end; rI(rN)=idx; rV(rN)=-(q{i,j}(ki, hi));
573 % RHS: departures to j==f with BB(m,i)==0 and nj<F(f)
580 idx = getP2Idx(f, nj, uj, i, ni+1, ki, m);
581 rN=rN+1;
if rN>rCap, rCap=2*rCap; rI(rCap)=0; rV(rCap)=0; end; rI(rN)=idx; rV(rN)=-(q{i,f}(ki, hi));
588 % RHS: unblocking when BB(m,i)==1 and MM(m,1)==i
590 if BB(m, i) == 1 && MM(m, 1) == i
596 idx = getP2Idx(f, F(f), kf, i, ni+1, ki, m);
597 rN=rN+1;
if rN>rCap, rCap=2*rCap; rI(rCap)=0; rV(rCap)=0; end; rI(rN)=idx; rV(rN)=-(q{f,w}(kf, pf));
606 nEq=nEq+1;
if eqNnz+rN>numel(eqI), eqI(2*(eqNnz+rN))=0; eqJ(2*(eqNnz+rN))=0; eqV(2*(eqNnz+rN))=0; end; eqI(eqNnz+(1:rN))=nEq; eqJ(eqNnz+(1:rN))=rI(1:rN); eqV(eqNnz+(1:rN))=rV(1:rN); eqNnz=eqNnz+rN;
if nEq>numel(beq), beq(2*nEq)=0; end
611 %% THM3f: Marginal balance
for i==f
612 if verbose; fprintf(
' THM3f (Marginal balance for finite queue) constraints...\n'); end
615 % LHS: arrivals from j<>f with BB(m,j)==0 and ni<F(f)
627 idx = getP2Idx(j, nj, kj, f, ni, uf, m);
628 rN=rN+1;
if rN>rCap, rCap=2*rCap; rI(rCap)=0; rV(rCap)=0; end; rI(rN)=idx; rV(rN)=q{j,f}(kj, hj);
638 % RHS: departures from f at ni+1 to j<>f (only m=1, no blocking)
648 idx = getP2Idx(j, nj, uj, f, ni+1, kf, 1);
649 rN=rN+1;
if rN>rCap, rCap=2*rCap; rI(rCap)=0; rV(rCap)=0; end; rI(rN)=idx; rV(rN)=-(q{f,j}(kf, hf));
657 nEq=nEq+1;
if eqNnz+rN>numel(eqI), eqI(2*(eqNnz+rN))=0; eqJ(2*(eqNnz+rN))=0; eqV(2*(eqNnz+rN))=0; end; eqI(eqNnz+(1:rN))=nEq; eqJ(eqNnz+(1:rN))=rI(1:rN); eqV(eqNnz+(1:rN))=rV(1:rN); eqNnz=eqNnz+rN;
if nEq>numel(beq), beq(2*nEq)=0; end
661 %% THM3I: Blocking depth balance (Theorem 4)
662 if verbose; fprintf(
' THM3I (Blocking depth balance) constraints...\n'); end
665 % LHS: arrivals to f at F(f) from j<>f with BB(m,j)==0 and ZZ(m)==z
675 if BB(m, j) == 0 && ZZ(m) == z
676 idx = getP2Idx(j, nj, kj, f, F(f), uf, m);
677 rN=rN+1;
if rN>rCap, rCap=2*rCap; rI(rCap)=0; rV(rCap)=0; end; rI(rN)=idx; rV(rN)=q{j,f}(kj, hj);
685 % RHS: departures from f to j<>f with ZZ(m)==z+1
696 idx = getP2Idx(j, nj, uj, f, F(f), kf, m);
697 rN=rN+1;
if rN>rCap, rCap=2*rCap; rI(rCap)=0; rV(rCap)=0; end; rI(rN)=idx; rV(rN)=-(q{f,j}(kf, hf));
706 nEq=nEq+1;
if eqNnz+rN>numel(eqI), eqI(2*(eqNnz+rN))=0; eqJ(2*(eqNnz+rN))=0; eqV(2*(eqNnz+rN))=0; end; eqI(eqNnz+(1:rN))=nEq; eqJ(eqNnz+(1:rN))=rI(1:rN); eqV(eqNnz+(1:rN))=rV(1:rN); eqNnz=eqNnz+rN;
if nEq>numel(beq), beq(2*nEq)=0; end
710 %% THM3L: Maximum blocking depth constraint
711 if verbose; fprintf(
' THM3L (Max blocking depth) constraints...\n'); end
717 % LHS: arrivals from j<>f with BB(m,j)==0 and MM1(m,j)>0
719 if j == f || BB(m, j) ~= 0 || MM1(m, j) <= 0
726 idx = getP2Idx(j, nj, kj, f, F(f), uf, m);
727 rN=rN+1;
if rN>rCap, rCap=2*rCap; rI(rCap)=0; rV(rCap)=0; end; rI(rN)=idx; rV(rN)=q{j,f}(kj, hj);
733 % RHS: uses MM1(m,j) to index into blocking configuration
735 if j == f || BB(m, j) ~= 0 || MM1(m, j) <= 0
738 mp = MM1(m, j); % blocking configuration index
743 idx = getP2Idx(f, F(f), kf, f, F(f), kf, mp);
744 rN=rN+1;
if rN>rCap, rCap=2*rCap; rI(rCap)=0; rV(rCap)=0; end; rI(rN)=idx; rV(rN)=-(q{f,w}(kf, uf));
751 nEq=nEq+1;
if eqNnz+rN>numel(eqI), eqI(2*(eqNnz+rN))=0; eqJ(2*(eqNnz+rN))=0; eqV(2*(eqNnz+rN))=0; end; eqI(eqNnz+(1:rN))=nEq; eqJ(eqNnz+(1:rN))=rI(1:rN); eqV(eqNnz+(1:rN))=rV(1:rN); eqNnz=eqNnz+rN;
if nEq>numel(beq), beq(2*nEq)=0; end
755 %% THM4: Queue-length bound inequality (Theorem 5)
756 if verbose; fprintf(
' THM4 (Queue-length bound) constraints...\n'); end
762 % LHS: sum_t sum_ht sum_nj sum_nt nt * p2
767 idx = getP2Idx(j, nj, kj, t, nt, ht, m);
768 rN=rN+1;
if rN>rCap, rCap=2*rCap; rI(rCap)=0; rV(rCap)=0; end; rI(rN)=idx; rV(rN)=nt;
777 idx = getP2Idx(j, nj, kj, i, ni, hi, m);
778 rN=rN+1;
if rN>rCap, rCap=2*rCap; rI(rCap)=0; rV(rCap)=0; end; rI(rN)=idx; rV(rN)=-(N);
782 nIn=nIn+1;
if inNnz+rN>numel(inI), inI(2*(inNnz+rN))=0; inJ(2*(inNnz+rN))=0; inV(2*(inNnz+rN))=0; end; inI(inNnz+(1:rN))=nIn; inJ(inNnz+(1:rN))=rI(1:rN); inV(inNnz+(1:rN))=-rV(1:rN); inNnz=inNnz+rN;
if nIn>numel(bineq), bineq(2*nIn)=0; end
789 % Materialize
the sparse constraint matrices from
the triplets.
790 Aeq = sparse(eqI(1:eqNnz), eqJ(1:eqNnz), eqV(1:eqNnz), nEq, nVars);
792 Aineq = sparse(inI(1:inNnz), inJ(1:inNnz), inV(1:inNnz), nIn, nVars);
793 bineq = bineq(1:nIn);
795 %% Build objective function
796 if verbose; fprintf(
'Building objective function...\n'); end
800 if strcmp(objective,
'U1min') || strcmp(objective,
'U1max')
803 error('Unknown objective: %s', objective);
806 targetQueue = objective;
809 % Utilization = sum over m, k, n of p2(i,n,k,i,n,k,m)
811 for ki = 1:K(targetQueue)
812 for ni = 1:F(targetQueue)
813 idx = getP2Idx(targetQueue, ni, ki, targetQueue, ni, ki, m);
819 if strcmp(sense, 'max')
824 if verbose; fprintf('Solving LP with %d variables and %d equality + %d inequality constraints...\n', ...
825 nVars, size(Aeq, 1), size(Aineq, 1)); end
827 % LP algorithm. R2025a's default 'dual-simplex-highs'
is broken in some
828 % installs (errors "Unrecognized field name optimstatus"), so an
829 % interior-point variant
is required here. On
the paper's BAS instance,
830 % which
is badly scaled (mu spans 1.016186 down to 2.585708e-05),
831 % 'interior-point-legacy'
is markedly more accurate than 'interior-point':
832 % against
the published GLPK optimum it gives |dU1min|=8.5e-07 and
833 % |dU1max|=9.2e-10, versus 4.3e-05 and 8.0e-06 for 'interior-point'. The
834 % residual
is pure solver tolerance, not formulation:
the minimum lands
835 % above and
the maximum below
the GLPK vertex, and both gaps shrink
836 % together as
the solver becomes more accurate. Override via
837 % params.lpAlgorithm if a particular model needs a different method.
838 if isfield(params, 'lpAlgorithm') && ~isempty(params.lpAlgorithm)
839 lpAlgorithm = params.lpAlgorithm;
841 lpAlgorithm = 'interior-point-legacy';
844 options = optimoptions('linprog', 'Display', 'final', 'Algorithm', lpAlgorithm);
846 options = optimoptions('linprog', 'Display', 'off', 'Algorithm', lpAlgorithm);
849 [x, fval, exitflag] = linprog(c, Aineq, bineq, Aeq, beq, lb, ub, options);
851 if strcmp(sense, 'max')
857 result.objective = fval;
858 result.exitflag = exitflag;
860 % Whether
the metric fields below can be filled
is a property of
the
861 % solution vector, not of exitflag. exitflag 0 (iteration limit) still
862 % returns a usable interior point -- that
is the normal outcome on badly
863 % scaled instances such as
the reference BAS network -- whereas a solve that
864 % breaks down returns an empty or non-finite x, which must never be
865 % consumed. Predicate on x accordingly, and say so rather than skipping
866 % silently. (Observed here: exitflag -4 comes back with x empty.)
867 hasSolution = ~isempty(x) && all(isfinite(x));
868 if ~isempty(x) && ~hasSolution
869 warning('qrf_bas:nonFiniteSolution', ...
870 'linprog returned a non-finite solution (exitflag %d); U and
the other metric fields are left unpopulated.', ...
875 % Compute utilizations
876 result.U = zeros(M, 1);
877 result.e = zeros(M, max(K));
881 result.e(i, ki) = x(getEIdx(i, ki));
887 idx = getP2Idx(i, ni, ki, i, ni, ki, m);
888 result.U(i) = result.U(i) + x(idx);
895 if verbose; fprintf('\n=== Results ===\n'); end
896 if verbose; fprintf('Objective value: %f\n', fval); end
897 if verbose; fprintf('Exit flag: %d\n', exitflag); end
898 if hasSolution && verbose
899 fprintf('\nUtilizations:\n');
901 fprintf(' Queue %d: U = %.6f\n', i, result.U(i));
903 fprintf('\nEffective utilizations by phase:\n');
905 fprintf(' Queue %d: e = [', i);
907 fprintf('%.6f ', result.e(i, ki));