LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
pfqn_sens_mva_validate.m
1function pfqn_sens_mva_validate()
2%{
3%{
4 % @file pfqn_sens_mva_validate.m
5 % @brief Validation harness for pfqn_sens_mva. Checks the MVA-like moment
6 % recursion of de Souza e Silva and Muntz (1988), Corollary 1, against
7 % three independent references:
8 %
9 % A. brute-force enumeration of the closed product-form equilibrium
10 % distribution (ground truth, mi==1);
11 % B. the differentiated-MVA Jacobian of pfqn_sens, via the identity
12 % Cov[n(i,r),n(i,s)] = L(i,s) * dQ(i,r)/dL(i,s) (covers mi>1);
13 % C. pfqn_mva for the base measures X, Q, U, R.
14 %
15 % It also reports the raw asymmetry of QCov before symmetrization: the
16 % recursion computes W(k,j;t,j) and W(t,j;k,j) by numerically distinct
17 % expressions, so their agreement is a nontrivial check of the formula.
18%}
19%}
20rng(0);
21tolBrute = 1e-9;
22tolSens = 1e-9;
23tolMva = 1e-10;
24tolSym = 1e-9;
25
26errBrute = 0; errSens = 0; errMva = 0; errSym = 0;
27nBrute = 0; nSens = 0;
28
29for trial = 1:40
30 M = randi([1 3]);
31 R = randi([1 3]);
32 L = 0.2 + rand(M,R);
33 N = randi([0 3],1,R);
34 if ~any(N > 0)
35 N(1) = 2;
36 end
37 if mod(trial,2) == 0
38 Z = 0.3 + rand(1,R);
39 else
40 Z = zeros(1,R);
41 end
42 % exercise a zero-demand column now and then: class r never visits station i
43 if mod(trial,5) == 0 && M > 1
44 L(1,1) = 0;
45 end
46
47 mom = pfqn_sens_mva(L,N,Z);
48
49 % ---- C. base measures against pfqn_mva -----------------------------
50 [XN,QN,UN,CN] = pfqn_mva(L,N,Z);
51 errMva = max([errMva, relerr(mom.X,XN), relerr(mom.Q,QN), ...
52 relerr(mom.U,UN), relerr(mom.R,CN)]);
53
54 % ---- A. brute force -------------------------------------------------
55 if prod(N+1) <= 64 && M <= 3
56 [Qb,QCovb] = brute_moments(L,N,Z);
57 errBrute = max([errBrute, relerr(mom.Q,Qb), relerr(mom.QCov,QCovb)]);
58 nBrute = nBrute + 1;
59 end
60
61 % ---- B. pfqn_sens Jacobian, and the raw asymmetry -------------------
62 for micase = 1:2
63 if micase == 1
64 mi = ones(1,M);
65 else
66 mi = randi([1 3],1,M);
67 end
68 momi = pfqn_sens_mva(L,N,Z,mi);
69 sens = pfqn_sens(L,N,Z,mi);
70 % Read the reference off the raw Jacobian, NOT off sens.QCov: pfqn_sens
71 % now sources its same-station blocks from pfqn_sens_mva, so comparing
72 % against sens.QCov would compare the recursion with itself.
73 pL = zeros(M,R);
74 for p = 1:numel(sens.params)
75 if sens.params(p).type == 'L'
76 pL(sens.params(p).station,sens.params(p).class) = p;
77 end
78 end
79 CovRef = zeros(M,R,R);
80 for i = 1:M
81 for r = 1:R
82 for s = 1:R
83 if pL(i,s) > 0
84 CovRef(i,r,s) = L(i,s) * sens.dQ(i,r,pL(i,s));
85 end
86 end
87 end
88 end
89 errSens = max(errSens, relerr(momi.QCov,CovRef));
90 errSym = max(errSym, momi.QCovAsym);
91 % Theorem 3: variance of the total queue length at a station
92 TotRef = zeros(M,1);
93 for i = 1:M
94 TotRef(i) = sum(sum(CovRef(i,:,:)));
95 end
96 errSens = max(errSens, relerr(momi.QTotVar,TotRef));
97 nSens = nSens + 1;
98 end
99end
100
101fprintf('\n=== pfqn_sens_mva validation (max relative error) ===\n');
102fprintf(' A. brute-force product form (%d models) : %.3e (tol %.1e)\n', nBrute, errBrute, tolBrute);
103fprintf(' B. pfqn_sens Jacobian (%d models) : %.3e (tol %.1e)\n', nSens, errSens, tolSens);
104fprintf(' C. pfqn_mva base measures : %.3e (tol %.1e)\n', errMva, tolMva);
105fprintf(' D. QCov symmetry (raw, pre-symmetrize) : %.3e (tol %.1e)\n', errSym, tolSym);
106
107ok = errBrute <= tolBrute && errSens <= tolSens && errMva <= tolMva && errSym <= tolSym;
108if ~ok
109 error('pfqn_sens_mva_validate:mismatch','one or more checks exceeded tolerance');
110end
111fprintf(' ALL CHECKS PASSED\n');
112end
113
114% =========================================================================
115function e = relerr(a,b)
116a = a(:); b = b(:);
117d = abs(a-b);
118scale = max(1, max(abs(a),abs(b)));
119e = max(d ./ scale);
120if isempty(e)
121 e = 0;
122end
123end
124
125% =========================================================================
126function [Q,QCov] = brute_moments(L,N,Z)
127% Exact moments by enumerating the closed product-form equilibrium
128% distribution. Stations 1..M are single-server fixed-rate centers; the think
129% time Z is an infinite-server station indexed 0 and carries no moment.
130% p(n) ~ prod_i [ n_i! prod_r L(i,r)^n(i,r)/n(i,r)! ] * prod_r Z(r)^n(0,r)/n(0,r)!
131[M,R] = size(L);
132states = enumerate_states(N,M); % each row: [n(1,1..R) n(2,1..R) ... n(M,1..R)]
133K = size(states,1);
134w = zeros(K,1);
135for k = 1:K
136 nir = reshape(states(k,:),R,M)'; % M x R
137 lw = 0;
138 ok = true;
139 for i = 1:M
140 ni = sum(nir(i,:));
141 lw = lw + gammaln(ni+1);
142 for r = 1:R
143 if nir(i,r) > 0
144 if L(i,r) <= 0
145 ok = false; break;
146 end
147 lw = lw + nir(i,r)*log(L(i,r)) - gammaln(nir(i,r)+1);
148 end
149 end
150 if ~ok, break; end
151 end
152 if ok
153 for r = 1:R
154 n0r = N(r) - sum(nir(:,r));
155 if n0r > 0
156 if Z(r) <= 0
157 ok = false; break;
158 end
159 lw = lw + n0r*log(Z(r)) - gammaln(n0r+1);
160 end
161 end
162 end
163 if ok
164 w(k) = exp(lw);
165 else
166 w(k) = 0;
167 end
168end
169w = w / sum(w);
170
171Q = zeros(M,R);
172QCov = zeros(M,R,R);
173for k = 1:K
174 nir = reshape(states(k,:),R,M)';
175 Q = Q + w(k)*nir;
176end
177for i = 1:M
178 for r = 1:R
179 for s = 1:R
180 m2 = 0;
181 for k = 1:K
182 nir = reshape(states(k,:),R,M)';
183 m2 = m2 + w(k)*nir(i,r)*nir(i,s);
184 end
185 QCov(i,r,s) = m2 - Q(i,r)*Q(i,s);
186 end
187 end
188end
189end
190
191% =========================================================================
192function states = enumerate_states(N,M)
193% All allocations of N(r) class-r jobs over M stations (the remainder sits in
194% the delay). Returns a matrix whose rows are [n(1,:) n(2,:) ... n(M,:)] with
195% the inner index running over stations for each class, flattened class-major.
196R = numel(N);
197per = cell(1,R);
198for r = 1:R
199 per{r} = compositions_leq(N(r),M); % rows: n(1..M,r) with sum <= N(r)
200end
201states = zeros(1,R*M);
202states = states([],:);
203idx = ones(1,R);
204while true
205 row = zeros(M,R);
206 for r = 1:R
207 row(:,r) = per{r}(idx(r),:)';
208 end
209 states(end+1,:) = reshape(row',1,[]); %#ok<AGROW>
210 r = R;
211 while r >= 1
212 idx(r) = idx(r) + 1;
213 if idx(r) <= size(per{r},1)
214 break;
215 end
216 idx(r) = 1;
217 r = r - 1;
218 end
219 if r == 0
220 break;
221 end
222end
223end
224
225% =========================================================================
226function C = compositions_leq(n,M)
227% All nonnegative integer vectors of length M summing to at most n.
228if M == 1
229 C = (0:n)';
230 return;
231end
232C = zeros(0,M);
233for first = 0:n
234 sub = compositions_leq(n-first,M-1);
235 C = [C; [repmat(first,size(sub,1),1), sub]]; %#ok<AGROW>
236end
237end
Definition Station.m:245