1function pfqn_sens_mvaldmx_validate()
4 % @file pfqn_sens_mvaldmx_validate.m
5 % @brief Validation harness
for pfqn_sens_mvaldmx,
the mixed load-dependent
6 % moment analysis of Akyildiz and Strelen (1991).
8 % Five independent references, chosen so that every channel of
the
9 % derivation
is exercised by something that does not share its code:
11 % A. pfqn_mvaldmx
for the base measures X, Q, U, R. The primal must be
12 % reproduced entry by entry, otherwise
the derivative
is of
the
14 % B. central finite differences of pfqn_mvaldmx with respect to
the
15 % demand-scaling parameter y(j,s). This checks
the differentiated
16 % recursion itself, including
the load-dependent channel
17 % dEC/dLo and
the open-
class channel dLo/dy of eq. (21), but does
18 % not check
the identity Cov = d nbar / dy.
19 % C. pfqn_sens_mva in
the closed load-independent limit. This checks
20 %
the identity against
the independently validated de Souza e Silva
21 % and Muntz recursion.
22 % D. brute-force enumeration of
the product-form equilibrium
23 % distribution. Closed load-dependent models are enumerated exactly;
24 % mixed models are enumerated with
the open populations truncated,
25 % which converges geometrically and
is therefore checked at a
26 % looser tolerance. This
is the only check that closes
the loop on
27 %
the identity in
the mixed load-dependent
case.
28 % E. symmetry of QCovFull. Cov[n(i,r),n(j,s)] and Cov[n(j,s),n(i,r)]
29 % are computed by differentiating two different classes
'
30 % equations, so their agreement is a nontrivial structural check.
37tolBrt = 1e-8; % exact enumeration, closed load-dependent
38tolBrtT = 5e-5; % truncated enumeration, mixed
41errMva = 0; errFd = 0; errMom = 0; errBrt = 0; errBrtT = 0; errSym = 0;
42nFd = 0; nMom = 0; nBrt = 0; nBrtT = 0;
44% =====================================================================
45% A/B/E on random mixed load-dependent models
46% =====================================================================
50 D = 0.2 + 0.6*rand(M,1+Ropen);
53 N(1) = randi([1 3]); % closed class
56 N(2) = Inf; % open class
57 lambda(2) = 0.05 + 0.15*rand; % keep the station loads modest
61 NCtot = sum(N(isfinite(N)));
62 % limited load dependence: rates grow up to level b then saturate
64 mu = zeros(M,max(NCtot,1));
67 mu(i,n) = min(n,b) * (0.8 + 0.4*rand);
70 % keep the geometric tail of the limited load dependence stable
73 Lo(i) = lambda*D(i,:)';
75 if any(Lo ./ mu(:,end) > 0.6)
79 mom = pfqn_sens_mvaldmx(lambda,D,N,Z,mu,ones(M,1));
81 % ---- A. base measures ------------------------------------------
82 [XN,QN,UN,CN] = pfqn_mvaldmx(lambda,D,N,Z,mu,ones(M,1));
83 errMva = max([errMva, relerr(mom.X,XN), relerr(mom.Q,QN), ...
84 relerr(mom.U,UN), relerr(mom.R,CN)]);
86 % ---- E. symmetry -----------------------------------------------
87 errSym = max(errSym, mom.QCovAsym);
89 % ---- B. finite differences -------------------------------------
93 if D(j,s) <= 0,
continue; end
94 Dp = D; Dp(j,s) = D(j,s)*(1+h);
95 Dm = D; Dm(j,s) = D(j,s)*(1-h);
96 [~,QNp] = pfqn_mvaldmx(lambda,Dp,N,Z,mu,ones(M,1));
97 [~,QNm] = pfqn_mvaldmx(lambda,Dm,N,Z,mu,ones(M,1));
98 fd = (QNp - QNm) / (2*h); % d nbar / dy at y=1
102 an(i,r) = mom.QCovFull(i,r,j,s);
105 errFd = max(errFd, relerr(an,fd));
111% =====================================================================
112% C. closed load-independent limit against pfqn_sens_mva
113% =====================================================================
118 N = randi([1 3],1,R);
122 mom = pfqn_sens_mvaldmx(lambda,D,N,Z,mu,ones(M,1));
123 ref = pfqn_sens_mva(D,N,Z);
124 errMom = max([errMom, relerr(mom.Q,ref.Q), relerr(mom.QCov,ref.QCov), ...
125 relerr(mom.QVar,ref.QVar), relerr(mom.QTotVar,ref.QTotVar)]);
129% =====================================================================
130% D. brute-force product form
131% =====================================================================
132% D1. closed load-dependent, exact enumeration
135 D = 0.3 + 0.5*rand(M,R);
136 N = randi([2 4],1,R);
140 mu = zeros(M,sum(N));
143 mu(i,n) = min(n,b) * (0.8 + 0.4*rand);
146 mom = pfqn_sens_mvaldmx(lambda,D,N,Z,mu,ones(M,1));
147 [Qb,QCovb] = brute_ldmx(lambda,D,N,Z,mu,0);
148 errBrt = max([errBrt, relerr(mom.Q,Qb), relerr(mom.QCov,QCovb)]);
152% D2. mixed load-dependent, truncated enumeration
155 D = 0.3 + 0.4*rand(M,R);
156 N = [randi([1 2]), Inf];
158 lambda = [0, 0.05 + 0.1*rand];
160 mu = zeros(M,sum(N(isfinite(N))));
163 mu(i,n) = min(n,b) * (1.0 + 0.3*rand);
168 Lo(i) = lambda*D(i,:)';
170 if any(Lo ./ mu(:,end) > 0.4)
173 mom = pfqn_sens_mvaldmx(lambda,D,N,Z,mu,ones(M,1));
174 [Qb,QCovb] = brute_ldmx(lambda,D,N,Z,mu,60);
175 errBrtT = max([errBrtT, relerr(mom.Q,Qb), relerr(mom.QCov,QCovb)]);
179fprintf('\n=== pfqn_sens_mvaldmx validation (max relative error) ===\n');
180fprintf(' A. pfqn_mvaldmx base measures : %.3e (tol %.1e)\n', errMva, tolMva);
181fprintf(' B. finite differences (%3d params) : %.3e (tol %.1e)\n', nFd, errFd, tolFd);
182fprintf(' C. pfqn_sens_mva closed LI (%3d models) : %.3e (tol %.1e)\n', nMom, errMom, tolMom);
183fprintf(' D1. brute force closed LD (%3d models) : %.3e (tol %.1e)\n', nBrt, errBrt, tolBrt);
184fprintf(' D2. brute force mixed LD (%3d models) : %.3e (tol %.1e)\n', nBrtT, errBrtT, tolBrtT);
185fprintf(' E. QCovFull symmetry (raw) : %.3e (tol %.1e)\n', errSym, tolSym);
187ok = errMva <= tolMva && errFd <= tolFd && errMom <= tolMom && ...
188 errBrt <= tolBrt && errBrtT <= tolBrtT && errSym <= tolSym;
190 error('pfqn_sens_mvaldmx_validate:mismatch','one or more checks exceeded tolerance');
192fprintf(' ALL CHECKS PASSED\n');
195% =========================================================================
196function e = relerr(a,b)
199scale = max(1, max(abs(a),abs(b)));
206% =========================================================================
207function [Q,QCov] = brute_ldmx(lambda,D,N,Z,mu,Kopen)
208% Exact moments by enumerating
the mixed load-dependent product form
209% p(n) ~ prod_i [ n_i! prod_r a(i,r)^n(i,r)/n(i,r)! prod_{j=1}^{n_i} 1/mu(i,j) ]
210% * prod_{closed c} Z(c)^n(0,c)/n(0,c)!
211% with a(i,r) = D(i,r)
for a closed
class and a(i,r) = lambda(r)*D(i,r)
for an
212% open
class, n_i
the total population at station i, and
the closed classes
213% constrained to sum to N. Open classes are truncated at Kopen jobs per station.
215openClasses = find(isinf(N));
216closedClasses = setdiff(1:R, openClasses);
221 a(:,r) = lambda(r) * D(:,r);
227% per-
class allocation lists
231 alloc{r} = compositions_leq(Kopen,M); % open: 0..Kopen total, free
233 alloc{r} = compositions_leq(N(r),M); % closed: remainder in
the delay
240 tot = tot * size(alloc{r},1);
249 nir(:,r) = alloc{r}(idx(r),:)
';
255 lw = lw + gammaln(ni+1);
257 lw = lw - log(mu_at(mu,i,j));
264 lw = lw + nir(i,r)*log(a(i,r)) - gammaln(nir(i,r)+1);
270 for ci = 1:numel(closedClasses)
271 c = closedClasses(ci);
272 n0c = N(c) - sum(nir(:,c));
277 lw = lw + n0c*log(Z(c)) - gammaln(n0c+1);
284 NIR(k,:) = reshape(nir',1,[]);
288 if idx(r) <= size(alloc{r},1)
302 Q = Q + W(k)*reshape(NIR(k,:),R,M)';
309 for k = 1:size(NIR,1)
310 nir = reshape(NIR(k,:),R,M)';
311 m2 = m2 + W(k)*nir(i,r)*nir(i,s);
313 QCov(i,r,s) = m2 - Q(i,r)*Q(i,s);
319% =========================================================================
320function v = mu_at(mu,i,j)
321% Limited load dependence:
the rate saturates at its last tabulated value.
329% =========================================================================
330function C = compositions_leq(n,M)
331% All nonnegative integer vectors of length M summing to at most n.
338 sub = compositions_leq(n-first,M-1);
339 C = [C; [repmat(first,size(sub,1),1), sub]]; %
#ok<AGROW>