1function
bool = convergedStoch(self, it)
2% BOOL = CONVERGEDSTOCH(IT)
4% Convergence controller
for stochastic layer
solvers (Robbins-Monro mode).
6% When one or more layer
solvers return noisy estimates (simulation, e.g.
7% JMT/SSA/LDES, or Monte Carlo integration, e.g. NC with mci/imci/ls),
the
8% deterministic Picard iteration in converged.m cannot terminate:
the
9% successive-difference error
is bounded below by
the standard error of
the
10% layer estimates, and
the layer-reset confirmation step merely resamples
11%
the noise. This routine implements a stochastic approximation iteration:
13% 1. Burn-in:
for the first stochiter_burnin iterations
the plain Picard
14% iteration runs with
the relaxation
factor configured at init, to move
15% quickly toward
the fixed point.
16% 2. Robbins-Monro step: afterwards
the relaxation
factor applied by
17% updateMetrics to
the fed-forward iterate (servt, residt, tput,
18% callservt) decays as omega_k = a0/k^alpha with alpha in (0.5,1].
19% Under
the contraction assumption already made by
the deterministic
20% iteration, and zero-mean noise with bounded variance,
the iterate
21% converges almost surely to
the true fixed point (Robbins and Monro,
22% 1951). Layer seeds are rotated per iteration in pre() so successive
23% evaluations observe independent noise.
24% 3. Polyak-Ruppert averaging: running averages of
the layer results and
25% of
the reported iterates are maintained and installed as
the final
26% solution in finish(), giving
the optimal O(1/sqrt(k)) rate and
27% robustness to
the choice of a0 (Polyak and Juditsky, 1992).
28% 4. Stopping: iteration stops when
the drift of
the averaged results
29% stays below iter_tol for stochiter_conseq consecutive iterations.
30% The drift of a running average decays like 1/k even under persistent
31% noise, so
the test terminates, and it self-calibrates: larger noise
32% keeps
the drift above tolerance longer, forcing more averaging.
34% Note:
the Robbins-Monro step acts through relax_omega, which
is applied
35% by updateMetricsDefault;
the moment3 update path does not use relaxation,
36% so
this controller
is primarily intended
for method
'default'.
43burnin = self.options.config.stochiter_burnin;
44a0 = self.options.config.stochiter_a0;
45alpha = self.options.config.stochiter_alpha;
47% Schedule
the Robbins-Monro step used by updateMetrics at
the next iteration
49 self.relax_omega = min(1.0, a0 / max(1, it - burnin + 1)^alpha);
53 % pure Picard burn-in; no averaging or convergence testing yet
54 self.maxitererr(it) = Inf;
55 if self.options.verbose
56 line_printf(sprintf(
'Stochastic iteration burn-in %d/%d.', it, burnin));
61if isempty(self.stochiter_start)
62 self.stochiter_start = it;
63 if self.options.verbose
64 line_printf('\b Started Robbins-Monro averaging (stochastic layer
solvers detected).');
68%% Polyak-Ruppert update of
the layer result averages and drift metric
69k = self.stoch_avg_count + 1;
71fields = {
'QN',
'UN',
'RN',
'TN',
'AN',
'WN'};
73 raw = self.results{end,e};
76 for f = 1:length(fields)
77 avg.(fields{f}) = raw.(fields{f});
80 prev = self.stoch_avg{e};
81 for f = 1:length(fields)
82 avg.(fields{f}) = polyak(prev.(fields{f}), raw.(fields{f}), k);
84 % drift of
the averaged queue lengths, normalized by population
85 N = sum(self.ensemble{e}.getNumberOfJobs);
87 d = abs(avg.QN(:) - prev.QN(:));
92 self.stoch_avg{e} = avg;
94self.stoch_avg_count = k;
96% Polyak-Ruppert averages of
the fed-forward iterates used in reporting
98 self.stoch_servt_avg = self.servt;
99 self.stoch_residt_avg = self.residt;
101 self.stoch_servt_avg = polyak(self.stoch_servt_avg, self.servt, k);
102 self.stoch_residt_avg = polyak(self.stoch_residt_avg, self.residt, k);
105self.maxitererr(it) = err;
106if self.options.verbose
107 line_printf(sprintf(
'RMIterErr=%.6e (tol=%.6e, omega=%.3f, k=%d)', ...
108 err, self.options.iter_tol, self.relax_omega, k));
111%% Stop when
the averaged-iterate drift stays below tolerance
112conseq = self.options.config.stochiter_conseq;
114 bool = all(self.maxitererr(it-conseq+1:it) < self.options.iter_tol);
115 self.hasconverged = bool;
119function m = polyak(prevv, raww, k)
120% M = POLYAK(PREVV, RAWW, K)
121% Running-mean update m = prev + (raw - prev)/k, robust to NaN entries
122% in either operand (a NaN sample leaves
the average untouched).
123m = prevv + (raww - prevv)/k;