1function [result, x, fval, exitflag] = mapqn_bnd_qr_ld(params, objective_queue, objective_phase, objective_n, sense)
2% MAPQN_BND_QR_LD - Quadratic Reduction Bounds
for Load-Dependent Systems
4% MATLAB port of bnd_qr_ld.py (Quadratic Reduction Bounds
for Load-Dependent
5% MAP queueing networks with load-dependent service rates).
8% [result, x, fval, exitflag] = mapqn_bnd_qr_ld(params, objective_queue, objective_phase, objective_n)
9% [result, x, fval, exitflag] = mapqn_bnd_qr_ld(params, objective_queue, objective_phase, objective_n, sense)
12% params - Structure with model parameters:
13% .M - Number of queues
14% .N - Total population
15% .K - [M x 1] Number of phases
for each queue
16% .mu - {M x 1} cell, each mu{i}
is K(i) x K(i) completion rates
17% .v - {M x 1} cell, each v{i}
is K(i) x K(i) background rates
18% .alpha - [M x N] load-dependent rates
19% .r - [M x M] Routing probabilities
20% .verbose - (optional)
boolean,
default true
22% objective_queue - Queue index
for objective (1-based, 1..M)
23% objective_phase - Phase index
for objective (1-based, 1..K(objective_queue))
24% objective_n - Population level
for objective (0..N)
25% sense - (optional)
'max' (
default) or
'min'
28% result - Structure with results:
29% .objective - Objective function value
30% .exitflag - Solver exit flag
31% .p2marginals - Marginal probabilities
32% x - Raw solution vector
33% fval - Objective function value
34% exitflag - Solver exit flag
36 if nargin < 5 || isempty(sense)
48 if isfield(params,
'verbose')
49 verbose = params.verbose;
54 % Validate objective indices
55 if objective_queue < 1 || objective_queue > M
56 error('objective_queue must be in range 1..%d', M);
58 if objective_phase < 1 || objective_phase > K(objective_queue)
59 error('objective_phase must be in range 1..%d', K(objective_queue));
61 if objective_n < 0 || objective_n > N
62 error('objective_n must be in range 0..%d', N);
65 %% Define q function (1-based indices, load-dependent)
66 % q_val(i,j,k,h,n): transition rate from queue i phase k to queue j phase h
67 % when queue i has n customers
68 q_val = @(i, j, k, h, n) q_func(i, j, k, h, n, mu, v, alpha, r);
70 %% Build variable indexing
71 % p2(j, nj, k, i, ni, h): joint probabilities
72 % j in 1:M, nj in 0:N, k in 1:K(j), i in 1:M, ni in 0:N, h in 1:K(i)
73 if verbose; fprintf('Building variable index
map...\n'); end
79 p2idx{j} = cell(N+1, K(j), M);
83 p2idx{j}{nj+1, kj, i} = zeros(N+1, K(i));
86 varCount = varCount + 1;
87 p2idx{j}{nj+1, kj, i}(ni+1, hi) = varCount;
96 if verbose; fprintf(
'Total variables: %d\n', nVars); end
99 getP2Idx = @(j, nj, kj, i, ni, hi) p2idx{j}{nj+1, kj, i}(ni+1, hi);
102 lb = zeros(nVars, 1);
111 if verbose; fprintf(
'Building constraints...\n'); end
113 %% ZERO constraints - fix infeasible states via ub=0
114 if verbose; fprintf(
' ZERO constraints...\n'); end
121 idx = getP2Idx(j, nj, kj, i, ni, hi);
123 % ZERO1: i==j, nj==ni, h~=k
124 if i == j && nj == ni && hi ~= kj
128 % ZERO2: i==j, nj~=ni
129 if i == j && nj ~= ni
133 % ZERO3: i~=j, nj+ni > N
134 if i ~= j && nj + ni > N
144 %% ONE: Normalization
145 % sum{nj,k} p2(j,nj,k,j,nj,k) = 1
for each j
146 if verbose; fprintf(
' ONE constraints...\n'); end
148 row = zeros(1, nVars);
151 idx = getP2Idx(j, nj, kj, j, nj, kj);
159 %% SYMMETRY: p2(i,ni,h,j,nj,k) = p2(j,nj,k,i,ni,h) (only one ordering)
160 if verbose; fprintf(
' SYMMETRY constraints...\n'); end
165 if i <= j && ~(i == j)
166 % skip: we only do i > j for ordering
172 if i ~= j && nj + ni > N
176 idx1 = getP2Idx(j, nj, kj, i, ni, hi);
177 idx2 = getP2Idx(i, ni, hi, j, nj, kj);
178 if ub(idx1) == 0 && ub(idx2) == 0
182 row = zeros(1, nVars);
195 %% MARGINALS: p2(j,nj,k,j,nj,k) = sum{ni=0..N-nj, h} p2(j,nj,k,i,ni,h)
for i~=j
196 if verbose; fprintf(
' MARGINALS constraints...\n'); end
204 row = zeros(1, nVars);
205 idx_diag = getP2Idx(j, nj, kj, j, nj, kj);
209 idx = getP2Idx(j, nj, kj, i, ni, hi);
210 row(idx) = row(idx) - 1;
220 %% THM1: Queue length theorem
221 %
for each (j,k): sum{i,nj>=1,ni>=1,h} ni*p2(j,nj,k,i,ni,h)
222 % - N*sum{nj>=1} p2(j,nj,k,j,nj,k) = 0
223 if verbose; fprintf(
' THM1 (Queue length) constraints...\n'); end
226 row = zeros(1, nVars);
227 % LHS: sum over i, nj>=1, ni>=1, h of ni*p2
232 idx = getP2Idx(j, nj, kj, i, ni, hi);
233 row(idx) = row(idx) + ni;
238 % RHS: -N * sum{nj>=1} p2(j,nj,k,j,nj,k)
240 idx = getP2Idx(j, nj, kj, j, nj, kj);
241 row(idx) = row(idx) - N;
248 %% THM1c: Empty queue theorem
249 %
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
250 if verbose; fprintf(
' THM1c (Empty queue) constraints...\n'); end
253 row = zeros(1, nVars);
258 idx = getP2Idx(j, 0, kj, i, ni, hi);
259 row(idx) = row(idx) + ni;
264 idx = getP2Idx(j, 0, kj, j, 0, kj);
265 row(idx) = row(idx) - N;
271 %% PC2: Second moment constraint
272 % sum{i,j,ni>=1,nj>=1,h,k} nj*ni*p2(j,nj,k,i,ni,h) = N^2
273 if verbose; fprintf(
' PC2 (Second moment) constraint...\n'); end
274 row = zeros(1, nVars);
281 idx = getP2Idx(j, nj, kj, i, ni, hi);
282 row(idx) = row(idx) + nj * ni;
292 %% THM2: Phase balance
293 %
for each (i,k): sum{j,h: j~=i or h~=k, ni>=1}
294 % (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
295 if verbose; fprintf(
' THM2 (Phase balance) constraints...\n'); end
298 row = zeros(1, nVars);
301 if ~(hi == ki && j == i)
303 q_out = q_val(i, j, ki, hi, ni);
304 q_in = q_val(i, j, hi, ki, ni);
305 idx_k = getP2Idx(i, ni, ki, i, ni, ki);
306 idx_h = getP2Idx(i, ni, hi, i, ni, hi);
307 row(idx_k) = row(idx_k) + q_out;
308 row(idx_h) = row(idx_h) - q_in;
318 %% THM3a: Flow balance
for ni = 1..N-1
319 %
for each (i, ni
in 1..N-1):
320 % sum{j~=i, k, h, u, nj=1..N-ni} q(j,i,k,h,nj)*p2(j,nj,k,i,ni,u)
321 % - sum{j~=i, k, h} q(i,j,k,h,ni+1)*p2(i,ni+1,k,i,ni+1,k) = 0
322 if verbose; fprintf(
' THM3a (Flow balance ni=1..N-1) constraints...\n'); end
325 row = zeros(1, nVars);
333 qv = q_val(j, i, kj, hj, nj);
334 idx = getP2Idx(j, nj, kj, i, ni, u);
335 row(idx) = row(idx) + qv;
347 qv = q_val(i, j, ki, hi, ni + 1);
348 idx = getP2Idx(i, ni + 1, ki, i, ni + 1, ki);
349 row(idx) = row(idx) - qv;
359 %% THM3b: Flow balance
for ni = 0
361 % sum{j~=i, k, h, nj=1..N} q(j,i,k,h,nj)*p2(j,nj,k,i,0,u)
362 % - sum{j~=i, k} q(i,j,k,u,1)*p2(i,1,k,i,1,k) = 0
363 if verbose; fprintf(
' THM3b (Flow balance ni=0) constraints...\n'); end
366 row = zeros(1, nVars);
367 % Incoming to empty queue
373 qv = q_val(j, i, kj, hj, nj);
374 idx = getP2Idx(j, nj, kj, i, 0, u);
375 row(idx) = row(idx) + qv;
381 % Outgoing from single customer
385 qv = q_val(i, j, ki, u, 1);
386 idx = getP2Idx(i, 1, ki, i, 1, ki);
387 row(idx) = row(idx) - qv;
396 %% QBAL: Queue balance (from AMPL bnd_quadraticreduction_ld.mod)
398 % LHS1: sum{h~=k, j, ni>=1} q(i,j,k,h,ni)*ni*p2(i,ni,k,i,ni,k)
399 % + LHS2: sum{j~=i, h, ni>=1} q(i,j,h,k,ni)*p2(i,ni,h,i,ni,h)
400 % = 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))
401 % + RHS2: sum{h~=k, j, ni>=1} q(i,j,h,k,ni)*ni*p2(i,ni,h,i,ni,h)
402 if verbose; fprintf(
' QBAL (Queue balance) constraints...\n'); end
405 row = zeros(1, nVars);
406 % LHS1: sum{h~=k, j, ni>=1} q(i,j,k,h,ni)*ni*p2(i,ni,k,i,ni,k)
411 qv = q_val(i, j, ki, hi, ni);
412 idx = getP2Idx(i, ni, ki, i, ni, ki);
413 row(idx) = row(idx) + qv * ni;
418 % LHS2: sum{j~=i, h, ni>=1} q(i,j,h,k,ni)*p2(i,ni,h,i,ni,h)
423 qv = q_val(i, j, hi, ki, ni);
424 idx = getP2Idx(i, ni, hi, i, ni, hi);
425 row(idx) = row(idx) + qv;
430 % -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)
436 qv = q_val(j, i, u, w, nj);
437 idx = getP2Idx(j, nj, u, i, 0, ki);
438 row(idx) = row(idx) - qv;
444 % -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)
450 qv = q_val(j, i, u, w, nj);
452 idx = getP2Idx(i, ni, ki, j, nj, u);
453 row(idx) = row(idx) - qv;
460 % -RHS2: sum{h~=k, j, ni>=1} q(i,j,h,k,ni)*ni*p2(i,ni,h,i,ni,h)
465 qv = q_val(i, j, hi, ki, ni);
466 idx = getP2Idx(i, ni, hi, i, ni, hi);
467 row(idx) = row(idx) - qv * ni;
477 %% COR1a: Correlation constraint (from AMPL bnd_quadraticreduction_ld.mod)
478 %
for each (i, kstar, ni_c
in 0..N-2)
479 if verbose; fprintf(
' COR1a constraints...\n'); end
483 row = zeros(1, nVars);
484 % 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)
492 qv = q_val(j, i, kj, hj, nj);
493 idx = getP2Idx(j, nj, kj, i, ni_c, u);
494 row(idx) = row(idx) + qv;
502 % 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)
508 qv = q_val(j, i, kj, hj, nj);
509 idx = getP2Idx(j, nj, kj, i, ni_c+1, kstar);
510 row(idx) = row(idx) + qv;
516 % C: sum{k~=kstar} q(i,i,kstar,k,ni_c+1)*p2(i,ni_c+1,kstar,i,ni_c+1,kstar)
519 qv = q_val(i, i, kstar, k2, ni_c+1);
520 idx = getP2Idx(i, ni_c+1, kstar, i, ni_c+1, kstar);
521 row(idx) = row(idx) + qv;
524 % -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)
529 qv = q_val(i, j, k2, k2, ni_c+1);
530 idx = getP2Idx(i, ni_c+1, k2, i, ni_c+1, k2);
531 row(idx) = row(idx) - qv;
536 % -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)
543 qv = q_val(i, j, k2, h2, ni_c+1);
544 idx = getP2Idx(i, ni_c+1, k2, i, ni_c+1, k2);
545 row(idx) = row(idx) - qv;
552 % -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)
557 qv = q_val(i, j, k2, kstar, ni_c+2);
558 idx = getP2Idx(i, ni_c+2, k2, i, ni_c+2, k2);
559 row(idx) = row(idx) - qv;
564 % -G: -sum{j~=i} q(i,j,kstar,kstar,ni_c+2)*p2(i,ni_c+2,kstar,i,ni_c+2,kstar)
567 qv = q_val(i, j, kstar, kstar, ni_c+2);
568 idx = getP2Idx(i, ni_c+2, kstar, i, ni_c+2, kstar);
569 row(idx) = row(idx) - qv;
572 % -H: -sum{k~=kstar} q(i,i,k,kstar,ni_c+1)*p2(i,ni_c+1,k,i,ni_c+1,k)
575 qv = q_val(i, i, k2, kstar, ni_c+1);
576 idx = getP2Idx(i, ni_c+1, k2, i, ni_c+1, k2);
577 row(idx) = row(idx) - qv;
586 %% COR1b: Correlation constraint boundary (from AMPL bnd_quadraticreduction_ld.mod)
587 %
for each (i, kstar)
588 if verbose; fprintf(
' COR1b constraints...\n'); end
591 row = zeros(1, nVars);
592 % A
': sum{j~=i,kj,hj,u~=kstar} q(j,i,kj,hj,1)*p2(j,1,kj,i,N-1,u)
599 qv = q_val(j, i, kj, hj, 1);
600 idx = getP2Idx(j, 1, kj, i, N-1, u);
601 row(idx) = row(idx) + qv;
608 % C': sum{k~=kstar} q(i,i,kstar,k,N)*p2(i,N,kstar,i,N,kstar)
611 qv = q_val(i, i, kstar, k2, N);
612 idx = getP2Idx(i, N, kstar, i, N, kstar);
613 row(idx) = row(idx) + qv;
616 % -D
': -sum{j~=i,k~=kstar} q(i,j,k,k,N)*p2(i,N,k,i,N,k)
621 qv = q_val(i, j, k2, k2, N);
622 idx = getP2Idx(i, N, k2, i, N, k2);
623 row(idx) = row(idx) - qv;
628 % -E': -sum{j~=i,k~=kstar,h~=k} q(i,j,k,h,N)*p2(i,N,k,i,N,k)
635 qv = q_val(i, j, k2, h2, N);
636 idx = getP2Idx(i, N, k2, i, N, k2);
637 row(idx) = row(idx) - qv;
644 % -H
': -sum{k~=kstar} q(i,i,k,kstar,N)*p2(i,N,k,i,N,k)
647 qv = q_val(i, i, k2, kstar, N);
648 idx = getP2Idx(i, N, k2, i, N, k2);
649 row(idx) = row(idx) - qv;
657 %% THM4: QMIN inequality constraint
658 % for each (j, k, i):
659 % 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
660 if verbose; fprintf(' THM4 (QMIN) constraints...\n
'); end
664 row = zeros(1, nVars);
665 % LHS: sum{t,h,nj,nt} nt*p2(j,nj,k,t,nt,h)
670 idx = getP2Idx(j, nj, kj, t, nt, ht);
671 row(idx) = row(idx) + nt;
676 % RHS: -N * sum{h,nj,ni} p2(j,nj,k,i,ni,h)
680 idx = getP2Idx(j, nj, kj, i, ni, hi);
681 row(idx) = row(idx) - N;
685 % >= 0 means -row*x <= 0
686 Aineq = [Aineq; -row];
692 %% Build objective function
693 if verbose; fprintf('Building objective function...\n
'); end
696 % Objective: p2(objective_queue, objective_n, objective_phase,
697 % objective_queue, objective_n, objective_phase)
698 idx_obj = getP2Idx(objective_queue, objective_n, objective_phase, ...
699 objective_queue, objective_n, objective_phase);
702 if strcmp(sense, 'max
')
703 c = -c; % linprog minimizes, so negate for maximization
708 fprintf('Solving LP with %d variables and %d equality + %d inequality constraints...\n
', ...
709 nVars, size(Aeq, 1), size(Aineq, 1));
713 options = optimoptions('linprog
', 'Display
', 'final', 'Algorithm
', 'interior-point
');
715 options = optimoptions('linprog
', 'Display
', 'off
', 'Algorithm
', 'interior-point
');
718 [x, fval, exitflag] = linprog(c, Aineq, bineq, Aeq, beq, lb, ub, options);
720 if strcmp(sense, 'max
')
726 result.objective = fval;
727 result.exitflag = exitflag;
730 % Extract marginal probabilities p2(j,nj,k,j,nj,k)
731 result.p2marginals = zeros(M, N+1, max(K));
735 idx = getP2Idx(j, nj, kj, j, nj, kj);
736 result.p2marginals(j, nj+1, kj) = x(idx);
742 if verbose; fprintf('\n=== Results ===\n
'); end
743 if verbose; fprintf('Objective value: %f\n
', fval); end
744 if verbose; fprintf('Exit flag: %d\n
', exitflag); end
745 if exitflag > 0 && verbose
746 fprintf('\nMarginal probabilities p2(j,nj,k,j,nj,k):\n
');
750 val = result.p2marginals(j, nj+1, kj);
752 fprintf(' p2(%d,%d,%d) = %.6f\n
', j, nj, kj, val);
760function qv = q_func(i, j, k, h, n, mu, v, alpha, r)
761% Q_FUNC - Compute load-dependent transition rate (1-based indices)
763% i: source queue (1-based)
764% j: destination queue (1-based)
765% k: source phase (1-based)
766% h: destination phase (1-based)
767% n: population at queue i (0 returns 0)
773 % Load-dependent scaling factor
774 if n <= size(alpha, 2)
775 alpha_val = alpha(i, n);
781 qv = r(i, j) * mu{i}(k, h) * alpha_val;
783 qv = (v{i}(k, h) + r(i, i) * mu{i}(k, h)) * alpha_val;