1function test_qrf_noblo_mmi_linear
2% Regression test
for the QRF no-blocking MMI polytope (linear assembly).
4% Self-contained: no LINE dependencies and no LP solver. It uses feasibility
5% of a known-exact point as
the oracle, which
is what actually discriminates
6%
the polytope (an fmincon iterate does not).
8% The instance
is the 2-station closed network with exponential service and
9% load-dependent rates alpha(i,n),
for which
the exact stationary law
is the
12% mu(2)*alpha(2,N-n) * g(n) = mu(1)*alpha(1,n+1) * g(n+1), g(n)=
P(n1=n).
14% Three properties are asserted:
15% 1. q
is 5-D, [M M max(K) max(K) N+1], per qrboundsrsrd_skel.mod.
16% 2. The exact point
is feasible.
17% 3. A point built from
the WRONG rates
is infeasible. This
is the property
18% that failed before
the 6-subscript-write / 4-subscript-read defect was
19% fixed: q then read as all zeros, every q-weighted row (THM1, THM30,
20% THM3, THM3f) was a vacuous 0=0 row, and
the polytope did not depend on
23N = 3; M = 2; rt = [0 1; 1 0];
27 struct(
'mu',[1.0 1.0],
'alpha',[1 1 1]), ...
28 struct(
'mu',[4.0 1.0],
'alpha',[1 1 1]), ...
29 struct(
'mu',[0.5 1.0],
'alpha',[1 1 1]), ...
30 struct(
'mu',[1.0 1.0],
'alpha',[1 2 3]), ...
31 struct(
'mu',[1.0 1.0],
'alpha',[1 3 9])};
35 alpha = ones(M,N); alpha(1,:) = cases{t}.alpha;
36 MAPs = {{-mu(1), mu(1)}; {-mu(2), mu(2)}};
38 [~,~,~,lp] = qrf_noblo_mmi_linear(MAPs,N,rt,alpha,
true);
41 q = local_q(MAPs,N,rt,alpha);
42 assert(isequal(size(q),[M M 1 1 N+1]), ...
43 'case %d: q must be 5-D [M M K K N+1], got %s', t, mat2str(size(q)));
45 % 2.
the exact point
is feasible
46 g = local_exact(N,mu,alpha);
47 x = local_point(N,M,g);
48 [rEq,rIn] = local_residuals(lp,x);
49 assert(rEq < tol,
'case %d: exact point violates an equality by %g', t, rEq);
50 assert(rIn < tol,
'case %d: exact point violates an inequality by %g', t, rIn);
52 % 3. a point built from
the wrong rates
is infeasible
53 gBad = local_exact(N,[mu(1)*3 mu(2)],alpha);
54 if max(abs(gBad-g)) > 1e-6
55 xBad = local_point(N,M,gBad);
56 rEqBad = local_residuals(lp,xBad);
57 assert(rEqBad > 1e-3, ...
58 [
'case %d: a point built from the wrong service rates is feasible ' ...
59 '(residual %g). The polytope does not depend on the rates.'], t, rEqBad);
63fprintf(
'test_qrf_noblo_mmi_linear: %d cases passed\n', numel(cases));
66function q = local_q(MAPs,N,rt,alpha)
67% Rebuild q exactly as qrf_noblo_mmi_linear does, to assert its arity.
69for i=1:M, K(i)=size(MAPs{i}{1},1); end
70q = zeros(M,M,max(K),max(K),N+1);
71for i = 1:M,
for j = 1:M,
for k = 1:K(i),
for h = 1:K(i),
for ni = 1:N
73 if k==h, v = 0;
else, v = MAPs{i}{1}(k,h); end
75 q(i,j,k,h,1+ni) = rt(i,j)*mu*alpha(i,ni);
77 q(i,j,k,h,1+ni) = v*alpha(i,ni)+rt(i,i)*mu*alpha(i,ni);
79end, end, end, end, end
82function g = local_exact(N,mu,alpha)
83% g(1+n) =
P(n1 = n)
for the 2-station load-dependent birth-death chain.
84g = zeros(1,N+1); g(1) = 1;
86 g(1+n+1) = g(1+n) * (mu(2)*alpha(2,N-n)) / (mu(1)*alpha(1,n+1));
91function x = local_point(N,M,g)
92% Assemble
the decision vector in
the layout used by deltap2/deltae,
for the
93% single-phase two-station
case: p2
is supported on n1+n2 = N.
95nx = M*(N+1)*Km*M*(N+1)*Km*MR + M*Km;
97gi = @(i,n) g(1 + (i==1)*n + (i==2)*(N-n)); %
P(n_i = n)
98for j = 1:M,
for nj = 0:N,
for i = 1:M,
for ni = 0:N
100 if nj == ni, val = gi(i,ni);
else, val = 0; end
102 if nj + ni == N, val = gi(i,ni);
else, val = 0; end
105 x(local_idx(N,M,j,1+nj,i,1+ni)) = val;
108% e(i,k) =
P(n_i >= 1)
109base = M*(N+1)*Km*M*(N+1)*Km*MR;
111 x(base + i) = 1 - gi(i,0);
115function idx = local_idx(N,M,j,nj,i,ni)
116MR = 1; Km = 1; k = 1; h = 1; m = 1;
117idx = (N+1)*Km*M*(N+1)*Km*MR*(j-1);
118idx = idx + Km*M*(N+1)*Km*MR*(nj-1);
119idx = idx + M*(N+1)*Km*MR*(k-1);
120idx = idx + (N+1)*Km*MR*(i-1);
121idx = idx + Km*MR*(ni-1);
126function [rEq,rIn] = local_residuals(lp,x)
129 rEq = max(abs(full(lp.Aeq*x - lp.beq(:))));
131if ~isempty(lp.A) && size(lp.A,1) > 0
132 rIn = max([0; full(lp.A*x - lp.b(:))]);