LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
convergedStoch.m
1function bool = convergedStoch(self, it)
2% BOOL = CONVERGEDSTOCH(IT)
3%
4% Convergence controller for stochastic layer solvers (Robbins-Monro mode).
5%
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:
12%
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.
33%
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'.
37
38bool = false;
39if it < 1
40 return
41end
42E = self.nlayers;
43burnin = self.options.config.stochiter_burnin;
44a0 = self.options.config.stochiter_a0;
45alpha = self.options.config.stochiter_alpha;
46
47% Schedule the Robbins-Monro step used by updateMetrics at the next iteration
48if it >= burnin
49 self.relax_omega = min(1.0, a0 / max(1, it - burnin + 1)^alpha);
50end
51
52if it <= burnin
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));
57 end
58 return
59end
60
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).');
65 end
66end
67
68%% Polyak-Ruppert update of the layer result averages and drift metric
69k = self.stoch_avg_count + 1;
70err = 0;
71fields = {'QN','UN','RN','TN','AN','WN'};
72for e = 1:E
73 raw = self.results{end,e};
74 avg = struct();
75 if k == 1
76 for f = 1:length(fields)
77 avg.(fields{f}) = raw.(fields{f});
78 end
79 else
80 prev = self.stoch_avg{e};
81 for f = 1:length(fields)
82 avg.(fields{f}) = polyak(prev.(fields{f}), raw.(fields{f}), k);
83 end
84 % drift of the averaged queue lengths, normalized by population
85 N = sum(self.ensemble{e}.getNumberOfJobs);
86 if N > 0
87 d = abs(avg.QN(:) - prev.QN(:));
88 d(isnan(d)) = 0;
89 err = err + max(d)/N;
90 end
91 end
92 self.stoch_avg{e} = avg;
93end
94self.stoch_avg_count = k;
95
96% Polyak-Ruppert averages of the fed-forward iterates used in reporting
97if k == 1
98 self.stoch_servt_avg = self.servt;
99 self.stoch_residt_avg = self.residt;
100else
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);
103end
104
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));
109end
110
111%% Stop when the averaged-iterate drift stays below tolerance
112conseq = self.options.config.stochiter_conseq;
113if k > conseq
114 bool = all(self.maxitererr(it-conseq+1:it) < self.options.iter_tol);
115 self.hasconverged = bool;
116end
117end
118
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;
124bad = isnan(m);
125m(bad) = raww(bad);
126bad = isnan(m);
127m(bad) = prevv(bad);
128end
Definition Station.m:245