1function pfqn_sens_mva_validate()
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:
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.
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.
26errBrute = 0; errSens = 0; errMva = 0; errSym = 0;
42 % exercise a zero-demand
column now and then: class r never
visits station i
43 if mod(trial,5) == 0 && M > 1
47 mom = pfqn_sens_mva(L,N,Z);
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)]);
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)]);
61 % ---- B. pfqn_sens Jacobian, and
the raw asymmetry -------------------
66 mi = randi([1 3],1,M);
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.
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;
79 CovRef = zeros(M,R,R);
84 CovRef(i,r,s) = L(i,s) * sens.dQ(i,r,pL(i,s));
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
94 TotRef(i) = sum(sum(CovRef(i,:,:)));
96 errSens = max(errSens, relerr(momi.QTotVar,TotRef));
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);
107ok = errBrute <= tolBrute && errSens <= tolSens && errMva <= tolMva && errSym <= tolSym;
109 error('pfqn_sens_mva_validate:mismatch','one or more checks exceeded tolerance');
111fprintf(' ALL CHECKS PASSED\n');
114% =========================================================================
115function e = relerr(a,b)
118scale = max(1, max(abs(a),abs(b)));
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)!
132states = enumerate_states(N,M); % each row: [n(1,1..R) n(2,1..R) ... n(M,1..R)]
136 nir = reshape(states(k,:),R,M)'; % M x R
141 lw = lw + gammaln(ni+1);
147 lw = lw + nir(i,r)*log(L(i,r)) - gammaln(nir(i,r)+1);
154 n0r = N(r) - sum(nir(:,r));
159 lw = lw + n0r*log(Z(r)) - gammaln(n0r+1);
174 nir = reshape(states(k,:),R,M)';
182 nir = reshape(states(k,:),R,M)';
183 m2 = m2 + w(k)*nir(i,r)*nir(i,s);
185 QCov(i,r,s) = m2 - Q(i,r)*Q(i,s);
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.
199 per{r} = compositions_leq(N(r),M); % rows: n(1..M,r) with sum <= N(r)
201states = zeros(1,R*M);
202states = states([],:);
207 row(:,r) = per{r}(idx(r),:)
';
209 states(end+1,:) = reshape(row',1,[]); %#ok<AGROW>
213 if idx(r) <= size(per{r},1)
225% =========================================================================
226function C = compositions_leq(n,M)
227% All nonnegative integer vectors of length M summing to at most n.
234 sub = compositions_leq(n-first,M-1);
235 C = [C; [repmat(first,size(sub,1),1), sub]]; %#ok<AGROW>