1function [result, x, fval, exitflag] = mapqn_bnd_qr_delay(params, objective_queue, objective_phase, objective_n, sense)
2% MAPQN_BND_QR_DELAY - Quadratic Reduction Bounds
for Delay Systems
4% MATLAB port of bnd_qr_delay.py (AMPL model bnd_quadraticreduction_delay.mod)
7% [result, x, fval, exitflag] = mapqn_bnd_qr_delay(params, objective_queue, objective_phase, objective_n)
8% [result, x, fval, exitflag] = mapqn_bnd_qr_delay(params, objective_queue, objective_phase, objective_n, sense)
11% params - Structure with model parameters:
12% .M - Number of queues
13% .N - Total population
14% .K - [M x 1] Number of phases
for each queue
15% .Z - Delay (think time) scalar
16% .D1 - Service demand at queue 1 scalar
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% .alpha - [M x N] load-dependent rates
20% .r - [M x M] Routing probabilities
21% .verbose - (optional)
boolean
23% objective_queue - Queue index
for objective (1-based, 1..M)
24% objective_phase - Phase index
for objective (1-based, 1..K(objective_queue))
25% objective_n - Population level
for objective (0..N)
26% sense - (optional)
'max' (
default) or
'min'
29% result - Structure with results:
30% .objective - Objective function value
31% .exitflag - Solver exit flag
32% .p2marginals - Marginal probabilities extracted from p2 diagonal
33% x - Raw solution vector
34% fval - Objective function value
35% exitflag - Solver exit flag
37 if nargin < 5 || isempty(sense)
51 if isfield(params,
'verbose')
52 verbose = params.verbose;
58 if objective_queue < 1 || objective_queue > M
59 error('objective_queue must be in range 1..%d', M);
61 if objective_phase < 1 || objective_phase > K(objective_queue)
62 error('objective_phase must be in range 1..%d', K(objective_queue));
64 if objective_n < 0 || objective_n > N
65 error('objective_n must be in range 0..%d', N);
68 % q function: load-dependent transition rate (1-based indexing)
69 % q_val(i, j, k, h, n): transition rate from queue i phase k to queue j phase h at population n
70 q_val = @(i, j, k, h, n) q_func(i, j, k, h, n, mu, v, r, alpha);
72 %% Build variable indexing
73 % p2(j, nj, k, i, ni, h) for j in 1:M, nj in 0:N, k in 1:K(j),
74 % i in 1:M, ni in 0:N, h in 1:K(i)
75 if verbose; fprintf('Building variable index
map...\n'); end
81 p2idx{j} = cell(N+1, K(j), M);
85 p2idx{j}{nj+1, kj, i} = zeros(N+1, K(i));
88 varCount = varCount + 1;
89 p2idx{j}{nj+1, kj, i}(ni+1, hi) = varCount;
98 if verbose; fprintf(
'Total variables: %d\n', nVars); end
101 getP2Idx = @(j, nj, kj, i, ni, hi) p2idx{j}{nj+1, kj, i}(ni+1, hi);
109 if verbose; fprintf(
'Building constraints...\n'); end
112 lb = zeros(nVars, 1);
115 %% ZERO constraints - fix infeasible states via ub=0
116 if verbose; fprintf(
' ZERO constraints...\n'); end
123 idx = getP2Idx(j, nj, kj, i, ni, hi);
125 % ZERO1: i==j, nj==ni, h~=k
126 if i == j && nj == ni && hi ~= kj
130 % ZERO2: i==j, nj~=ni
131 if i == j && nj ~= ni
135 % ZERO3: i~=j, nj+ni > N
136 if i ~= j && nj + ni > N
146 %% ONE: Normalization
147 % sum{nj=0..N, k=1..K(j)} p2(j,nj,k,j,nj,k) = 1
for each j
148 if verbose; fprintf(
' ONE constraints...\n'); end
150 row = zeros(1, nVars);
153 idx = getP2Idx(j, nj, kj, j, nj, kj);
161 %% SYMMETRY: p2(i,ni,h,j,nj,k) = p2(j,nj,k,i,ni,h)
162 % Only add
for one ordering to avoid redundancy
163 if verbose; fprintf(
' SYMMETRY constraints...\n'); end
168 if i <= j && ~(i == j)
169 % skip: only add when i > j to avoid redundancy
170 % actually we want i > j
176 if i ~= j && nj + ni > N
180 idx1 = getP2Idx(j, nj, kj, i, ni, hi);
181 idx2 = getP2Idx(i, ni, hi, j, nj, kj);
182 if ub(idx1) == 0 && ub(idx2) == 0
186 row = zeros(1, nVars);
199 %% MARGINALS: p2(j,nj,k,j,nj,k) = sum{ni=0..N-nj, h=1..K(i)} p2(j,nj,k,i,ni,h)
for each (j,k,nj,i~=j)
200 if verbose; fprintf(
' MARGINALS constraints...\n'); end
208 row = zeros(1, nVars);
209 idx_diag = getP2Idx(j, nj, kj, j, nj, kj);
213 idx = getP2Idx(j, nj, kj, i, ni, hi);
214 row(idx) = row(idx) - 1;
224 %% PC2: sum{i,j,ni>=1,nj>=1,h,k} nj*ni*p2(j,nj,k,i,ni,h) = N^2
225 if verbose; fprintf(
' PC2 (Second moment) constraint...\n'); end
226 row = zeros(1, nVars);
233 idx = getP2Idx(j, nj, kj, i, ni, hi);
234 row(idx) = row(idx) + nj * ni;
244 %% THM1: Queue length theorem
245 %
for each (j,k): sum{i,nj>=1,ni>=1,h} ni*p2(j,nj,k,i,ni,h) - N*sum{nj>=1} p2(j,nj,k,j,nj,k) = 0
246 if verbose; fprintf(
' THM1 (Queue length) constraints...\n'); end
249 row = zeros(1, nVars);
250 % Left side: sum ni*p2(j,nj,k,i,ni,h)
for nj>=1, ni>=1
255 idx = getP2Idx(j, nj, kj, i, ni, hi);
256 row(idx) = row(idx) + ni;
261 % Right side: -N * sum p2(j,nj,k,j,nj,k)
for nj>=1
263 idx = getP2Idx(j, nj, kj, j, nj, kj);
264 row(idx) = row(idx) - N;
271 %% THM1c: Empty queue theorem
272 %
for each (j,k): sum{i,ni>=1,h} ni*p2(j,0,k,i,ni,h) - N*p2(j,0,k,j,0,k) = 0
273 if verbose; fprintf(
' THM1c (Empty queue) constraints...\n'); end
276 row = zeros(1, nVars);
281 idx = getP2Idx(j, 0, kj, i, ni, hi);
282 row(idx) = row(idx) + ni;
287 idx = getP2Idx(j, 0, kj, j, 0, kj);
288 row(idx) = row(idx) - N;
294 %% XZ: Delay constraint
295 % sum{ni>=1,k} ni*p2(M,ni,k,M,ni,k) - (Z/D1)*sum{k,nj>=1} p2(1,nj,k,1,nj,k) = 0
296 if verbose; fprintf(
' XZ (Delay) constraint...\n'); end
297 row = zeros(1, nVars);
298 % Left side: queue length at delay (queue M)
301 idx = getP2Idx(M, ni, kM, M, ni, kM);
302 row(idx) = row(idx) + ni;
305 % Right side: -(Z/D1) * throughput at queue 1
308 idx = getP2Idx(1, nj, kj, 1, nj, kj);
309 row(idx) = row(idx) - Z / D1;
315 %% THM2: Phase balance
316 %
for each (i,k): sum{j,h: j~=i or h~=k, ni>=1} (q(i,j,k,h,ni)*p2(i,ni,k,i,ni,k) - q(i,j,h,k,ni)*p2(i,ni,h,i,ni,h)) = 0
317 if verbose; fprintf(
' THM2 (Phase balance) constraints...\n'); end
320 row = zeros(1, nVars);
323 if ~(hi == ki && j == i)
325 q_out = q_val(i, j, ki, hi, ni);
326 q_in = q_val(i, j, hi, ki, ni);
327 idx_k = getP2Idx(i, ni, ki, i, ni, ki);
328 idx_h = getP2Idx(i, ni, hi, i, ni, hi);
329 row(idx_k) = row(idx_k) + q_out;
330 row(idx_h) = row(idx_h) - q_in;
340 %% THM3a: Flow balance
for ni=1..N-1
341 %
for each (i,ni): sum{j~=i,k,h,u,nj=1..N-ni} q(j,i,k,h,nj)*p2(j,nj,k,i,ni,u)
342 % - sum{j~=i,k,h} q(i,j,k,h,ni+1)*p2(i,ni+1,k,i,ni+1,k) = 0
343 if verbose; fprintf(
' THM3a (Flow balance) constraints...\n'); end
346 row = zeros(1, nVars);
354 qv = q_val(j, i, kj, hj, nj);
355 idx = getP2Idx(j, nj, kj, i, ni, ui);
356 row(idx) = row(idx) + qv;
368 qv = q_val(i, j, ki, hi, ni+1);
369 idx = getP2Idx(i, ni+1, ki, i, ni+1, ki);
370 row(idx) = row(idx) - qv;
380 %% THM3b: Flow balance
for ni=0
381 %
for each (i,u): sum{j~=i,k,h,nj=1..N} q(j,i,k,h,nj)*p2(j,nj,k,i,0,u)
382 % - sum{j~=i,k} q(i,j,k,u,1)*p2(i,1,k,i,1,k) = 0
383 if verbose; fprintf(
' THM3b (Flow balance ni=0) constraints...\n'); end
386 row = zeros(1, nVars);
387 % Incoming to empty queue
393 qv = q_val(j, i, kj, hj, nj);
394 idx = getP2Idx(j, nj, kj, i, 0, ui);
395 row(idx) = row(idx) + qv;
401 % Outgoing from single customer
405 qv = q_val(i, j, ki, ui, 1);
406 idx = getP2Idx(i, 1, ki, i, 1, ki);
407 row(idx) = row(idx) - qv;
416 %% QBAL: Queue balance (from AMPL bnd_quadraticreduction_delay.mod)
418 % LHS1: sum{h~=k, j, ni>=1} q(i,j,k,h,ni)*ni*p2(i,ni,k,i,ni,k)
419 % + LHS2: sum{j~=i, h, ni>=1} q(i,j,h,k,ni)*p2(i,ni,h,i,ni,h)
420 % = RHS1: sum{j~=i, u in K(j), w in K(j), nj>=1} q(j,i,u,w,nj)*(p2(j,nj,u,i,0,k) + sum{ni>=1} p2(i,ni,k,j,nj,u))
421 % + RHS2: sum{h~=k, j, ni>=1} q(i,j,h,k,ni)*ni*p2(i,ni,h,i,ni,h)
422 if verbose; fprintf(
' QBAL (Queue balance) constraints...\n'); end
425 row = zeros(1, nVars);
426 % LHS1: sum{h~=k, j, ni>=1} q(i,j,k,h,ni)*ni*p2(i,ni,k,i,ni,k)
431 qv = q_val(i, j, ki, hi, ni);
432 idx = getP2Idx(i, ni, ki, i, ni, ki);
433 row(idx) = row(idx) + qv * ni;
438 % LHS2: sum{j~=i, h, ni>=1} q(i,j,h,k,ni)*p2(i,ni,h,i,ni,h)
443 qv = q_val(i, j, hi, ki, ni);
444 idx = getP2Idx(i, ni, hi, i, ni, hi);
445 row(idx) = row(idx) + qv;
450 % -RHS1 part a: sum{j~=i, u in K(j), w in K(j), nj>=1} q(j,i,u,w,nj)*p2(j,nj,u,i,0,k)
456 qv = q_val(j, i, u, w, nj);
457 idx = getP2Idx(j, nj, u, i, 0, ki);
458 row(idx) = row(idx) - qv;
464 % -RHS1 part b: sum{j~=i, u in K(j), w in K(j), nj>=1, ni>=1} q(j,i,u,w,nj)*p2(i,ni,k,j,nj,u)
470 qv = q_val(j, i, u, w, nj);
472 idx = getP2Idx(i, ni, ki, j, nj, u);
473 row(idx) = row(idx) - qv;
480 % -RHS2: sum{h~=k, j, ni>=1} q(i,j,h,k,ni)*ni*p2(i,ni,h,i,ni,h)
485 qv = q_val(i, j, hi, ki, ni);
486 idx = getP2Idx(i, ni, hi, i, ni, hi);
487 row(idx) = row(idx) - qv * ni;
497 %% COR1a: Correlation constraint (from AMPL bnd_quadraticreduction_delay.mod)
498 %
for each (i, kstar, ni_c
in 0..N-2)
499 if verbose; fprintf(
' COR1a constraints...\n'); end
503 row = zeros(1, nVars);
504 % A: sum{j~=i,kj,hj,u~=kstar,nj=1..N-ni_c} q(j,i,kj,hj,nj)*p2(j,nj,kj,i,ni_c,u)
512 qv = q_val(j, i, kj, hj, nj);
513 idx = getP2Idx(j, nj, kj, i, ni_c, u);
514 row(idx) = row(idx) + qv;
522 % B: sum{j~=i,kj,hj,nj=1..N-ni_c} q(j,i,kj,hj,nj)*p2(j,nj,kj,i,ni_c+1,kstar)
528 qv = q_val(j, i, kj, hj, nj);
529 idx = getP2Idx(j, nj, kj, i, ni_c+1, kstar);
530 row(idx) = row(idx) + qv;
536 % C: sum{k~=kstar} q(i,i,kstar,k,ni_c+1)*p2(i,ni_c+1,kstar,i,ni_c+1,kstar)
539 qv = q_val(i, i, kstar, k2, ni_c+1);
540 idx = getP2Idx(i, ni_c+1, kstar, i, ni_c+1, kstar);
541 row(idx) = row(idx) + qv;
544 % -D: -sum{j~=i,k~=kstar} q(i,j,k,k,ni_c+1)*p2(i,ni_c+1,k,i,ni_c+1,k)
549 qv = q_val(i, j, k2, k2, ni_c+1);
550 idx = getP2Idx(i, ni_c+1, k2, i, ni_c+1, k2);
551 row(idx) = row(idx) - qv;
556 % -E: -sum{j~=i,k~=kstar,h~=k} q(i,j,k,h,ni_c+1)*p2(i,ni_c+1,k,i,ni_c+1,k)
563 qv = q_val(i, j, k2, h2, ni_c+1);
564 idx = getP2Idx(i, ni_c+1, k2, i, ni_c+1, k2);
565 row(idx) = row(idx) - qv;
572 % -F: -sum{j~=i,k~=kstar} q(i,j,k,kstar,ni_c+2)*p2(i,ni_c+2,k,i,ni_c+2,k)
577 qv = q_val(i, j, k2, kstar, ni_c+2);
578 idx = getP2Idx(i, ni_c+2, k2, i, ni_c+2, k2);
579 row(idx) = row(idx) - qv;
584 % -G: -sum{j~=i} q(i,j,kstar,kstar,ni_c+2)*p2(i,ni_c+2,kstar,i,ni_c+2,kstar)
587 qv = q_val(i, j, kstar, kstar, ni_c+2);
588 idx = getP2Idx(i, ni_c+2, kstar, i, ni_c+2, kstar);
589 row(idx) = row(idx) - qv;
592 % -H: -sum{k~=kstar} q(i,i,k,kstar,ni_c+1)*p2(i,ni_c+1,k,i,ni_c+1,k)
595 qv = q_val(i, i, k2, kstar, ni_c+1);
596 idx = getP2Idx(i, ni_c+1, k2, i, ni_c+1, k2);
597 row(idx) = row(idx) - qv;
606 %% COR1b: Correlation constraint boundary (from AMPL bnd_quadraticreduction_delay.mod)
607 %
for each (i, kstar)
608 if verbose; fprintf(
' COR1b constraints...\n'); end
611 row = zeros(1, nVars);
612 % A
': sum{j~=i,kj,hj,u~=kstar} q(j,i,kj,hj,1)*p2(j,1,kj,i,N-1,u)
619 qv = q_val(j, i, kj, hj, 1);
620 idx = getP2Idx(j, 1, kj, i, N-1, u);
621 row(idx) = row(idx) + qv;
628 % C': sum{k~=kstar} q(i,i,kstar,k,N)*p2(i,N,kstar,i,N,kstar)
631 qv = q_val(i, i, kstar, k2, N);
632 idx = getP2Idx(i, N, kstar, i, N, kstar);
633 row(idx) = row(idx) + qv;
636 % -D
': -sum{j~=i,k~=kstar} q(i,j,k,k,N)*p2(i,N,k,i,N,k)
641 qv = q_val(i, j, k2, k2, N);
642 idx = getP2Idx(i, N, k2, i, N, k2);
643 row(idx) = row(idx) - qv;
648 % -E': -sum{j~=i,k~=kstar,h~=k} q(i,j,k,h,N)*p2(i,N,k,i,N,k)
655 qv = q_val(i, j, k2, h2, N);
656 idx = getP2Idx(i, N, k2, i, N, k2);
657 row(idx) = row(idx) - qv;
664 % -H
': -sum{k~=kstar} q(i,i,k,kstar,N)*p2(i,N,k,i,N,k)
667 qv = q_val(i, i, k2, kstar, N);
668 idx = getP2Idx(i, N, k2, i, N, k2);
669 row(idx) = row(idx) - qv;
677 %% THM4: QMIN inequality constraint
678 % for each (j,k,i): sum{t,h,nj,nt} nt*p2(j,nj,k,t,nt,h) - N*sum{h,nj,ni} p2(j,nj,k,i,ni,h) >= 0
679 if verbose; fprintf(' THM4 (QMIN) constraints...\n
'); end
683 row = zeros(1, nVars);
684 % Left side: total queue length
689 idx = getP2Idx(j, nj, kj, t, nt, ht);
690 row(idx) = row(idx) + nt;
695 % Right side: -N * probability mass at queue i
699 idx = getP2Idx(j, nj, kj, i, ni, hi);
700 row(idx) = row(idx) - N;
704 % row >= 0 => -row <= 0
705 Aineq = [Aineq; -row];
711 %% Build objective function
712 % maximize p2(objective_queue, objective_n, objective_phase, objective_queue, objective_n, objective_phase)
713 if verbose; fprintf('Building objective function...\n
'); end
715 idx_obj = getP2Idx(objective_queue, objective_n, objective_phase, objective_queue, objective_n, objective_phase);
718 if strcmp(sense, 'max
')
719 c = -c; % linprog minimizes, so negate for maximization
723 if verbose; fprintf('Solving LP with %d variables and %d equality + %d inequality constraints...\n
', ...
724 nVars, size(Aeq, 1), size(Aineq, 1)); end
727 options = optimoptions('linprog
', 'Display
', 'final', 'Algorithm
', 'interior-point
');
729 options = optimoptions('linprog
', 'Display
', 'off
', 'Algorithm
', 'interior-point
');
732 [x, fval, exitflag] = linprog(c, Aineq, bineq, Aeq, beq, lb, ub, options);
734 if strcmp(sense, 'max
')
740 result.objective = fval;
741 result.exitflag = exitflag;
744 % Extract marginal probabilities from p2 diagonal
745 % p2marginals{j}(nj, kj) = p2(j, nj, kj, j, nj, kj)
746 result.p2marginals = cell(M, 1);
748 result.p2marginals{j} = zeros(N+1, K(j));
751 idx = getP2Idx(j, nj, kj, j, nj, kj);
752 result.p2marginals{j}(nj+1, kj) = x(idx);
758 if verbose; fprintf('\n=== Results ===\n
'); end
759 if verbose; fprintf('Objective value: %f\n
', fval); end
760 if verbose; fprintf('Exit flag: %d\n
', exitflag); end
761 if exitflag > 0 && verbose
762 fprintf('\nMarginal probabilities (diagonal):\n
');
764 fprintf(' Queue %d:\n
', j);
767 val = result.p2marginals{j}(nj+1, kj);
769 fprintf(' p2(%d,%d,%d) = %.6f\n
', j, nj, kj, val);
777function qv = q_func(i, j, k, h, n, mu, v, r, alpha)
778% Q_FUNC - Compute load-dependent transition rate (1-based indexing)
780% i: source queue (1-based)
781% j: destination queue (1-based)
782% k: source phase (1-based)
783% h: destination phase (1-based)
784% n: population level (0 returns 0)
791 % Load-dependent scaling
792 if n <= size(alpha, 2)
793 alpha_val = alpha(i, n);
799 qv = r(i, j) * mu{i}(k, h) * alpha_val;
801 qv = (v{i}(k, h) + r(i, i) * mu{i}(k, h)) * alpha_val;