1function
bool = converged(self, it)
4% Apply convergence test to the SolverLN iterations. As the solver keeps
5% iterating,
this method maintains a moving avg of the recent results based
6% on which it averages across the layer the maximum queue-length error.
7% Convergence
is tested by resetting all layers (to avoid caching) and
8% doing an extra iteration. If the iteration keeps fulfilling the error
9% requirements
for convergence, then the solver completes.
13%% Stochastic iteration dispatch
14% see _kb/06-solver-catalog.md (LN section)
for the rationale
15if ~isempty(self.stochiter_mode)
16 if self.stochiter_auto && strcmp(self.stochiter_mode,'off') && it >= 1 && any(self.stochlayers)
17 % a layer with method 'default' resolved at runtime to a
18 % stochastic method (captured in analyze() at iteration 1)
19 self.stochiter_mode = 'rm';
20 line_debug('LN: stochastic layer method detected at runtime, switching to Robbins-Monro iteration');
22 if strcmp(self.stochiter_mode,'rm')
23 bool = convergedStoch(self, it);
28iter_min = max([2*length(self.model.ensemble),ceil(self.options.iter_max/4)]);
30results = self.results; %
#ok<NASGU> % faster in matlab
32%% Start moving average to help convergence
34if false%it>self.averagingstart%<self.averagingstart+50
35 % In the first 50 averaging iterations use Cesaro summation
36 if ~isempty(self.averagingstart)
37 if it>=iter_min % assume steady-state
39 wnd_size_max = (it-self.averagingstart+1);
40 sk_q = cell(1,wnd_size_max);
41 sk_u = cell(1,wnd_size_max);
42 sk_r = cell(1,wnd_size_max);
43 sk_t = cell(1,wnd_size_max);
44 sk_a = cell(1,wnd_size_max);
45 sk_w = cell(1,wnd_size_max);
46 % compute all partial sumbs of up to wnd_size_max elements
49 sk_q{k} = results{self.averagingstart,e}.QN;
50 sk_u{k} = results{self.averagingstart,e}.UN;
51 sk_r{k} = results{self.averagingstart,e}.RN;
52 sk_t{k} = results{self.averagingstart,e}.TN;
53 sk_a{k} = results{self.averagingstart,e}.AN;
54 sk_w{k} = results{self.averagingstart,e}.WN;
56 sk_q{k} = results{self.averagingstart+k-1,e}.QN/k + sk_q{k-1}*(k-1)/k;
57 sk_u{k} = results{self.averagingstart+k-1,e}.UN/k + sk_u{k-1}*(k-1)/k;
58 sk_r{k} = results{self.averagingstart+k-1,e}.RN/k + sk_r{k-1}*(k-1)/k;
59 sk_t{k} = results{self.averagingstart+k-1,e}.TN/k + sk_t{k-1}*(k-1)/k;
60 sk_a{k} = results{self.averagingstart+k-1,e}.AN/k + sk_a{k-1}*(k-1)/k;
61 sk_w{k} = results{self.averagingstart+k-1,e}.WN/k + sk_w{k-1}*(k-1)/k;
64 results{end,e}.QN = cellsum(sk_q)/wnd_size_max;
65 results{end,e}.UN = cellsum(sk_u)/wnd_size_max;
66 results{end,e}.RN = cellsum(sk_r)/wnd_size_max;
67 results{end,e}.TN = cellsum(sk_t)/wnd_size_max;
68 results{end,e}.AN = cellsum(sk_a)/wnd_size_max;
69 results{end,e}.WN = cellsum(sk_w)/wnd_size_max;
74 wnd_size = max(5,ceil(iter_min/5)); % moving window size
75 mov_avg_weight = 1/wnd_size;
76 results = self.results; % faster in matlab
77 if it>=iter_min % assume steady-state
79 results{end,e}.QN = mov_avg_weight*results{end,e}.QN;
80 results{end,e}.UN = mov_avg_weight*results{end,e}.UN;
81 results{end,e}.RN = mov_avg_weight*results{end,e}.RN;
82 results{end,e}.TN = mov_avg_weight*results{end,e}.TN;
83 results{end,e}.AN = mov_avg_weight*results{end,e}.AN;
84 results{end,e}.WN = mov_avg_weight*results{end,e}.WN;
86 results{end,e}.QN = results{end,e}.QN + results{end-k,e}.QN * mov_avg_weight;
87 results{end,e}.UN = results{end,e}.UN + results{end-k,e}.UN * mov_avg_weight;
88 results{end,e}.RN = results{end,e}.RN + results{end-k,e}.RN * mov_avg_weight;
89 results{end,e}.TN = results{end,e}.TN + results{end-k,e}.TN * mov_avg_weight;
90 results{end,e}.AN = results{end,e}.AN + results{end-k,e}.AN * mov_avg_weight;
91 results{end,e}.WN = results{end,e}.WN + results{end-k,e}.WN * mov_avg_weight;
96self.results = results;
98%% Take as error metric the max qlen-error averaged across layers
100 self.maxitererr(it) = 0;
102 metric = results{end,e}.QN;
103 metric_1 = results{end-1,e}.QN;
104 N = sum(self.ensemble{e}.getNumberOfJobs);
107 diffvals = abs(metric(:) - metric_1(:));
108 diffvals(isnan(diffvals)) = 0;
109 IterErr = max(diffvals)/N;
113 self.maxitererr(it) = self.maxitererr(it) + IterErr;
115 %
if self.options.verbose
116 %
if self.solvers{e}.options.verbose
117 % line_printf(sprintf(
'QLen change: %f.\n',self.maxitererr(it)/E));
124 if self.options.verbose
125 line_printf(
'\b Started averaging to aid convergence.');
127 self.averagingstart = it;
130 % Print iteration error
for tracing
131 if self.options.verbose
132 line_printf(sprintf(
'MaxIterErr=%.6e (tol=%.6e, hasconv=%d)', ...
133 self.maxitererr(it), self.options.iter_tol, self.hasconverged));
136 %% Update relaxation
factor for adaptive/
auto modes
137 relax_mode = self.options.config.relax;
138 if strcmpi(relax_mode,
'adaptive') || strcmpi(relax_mode,
'auto')
139 % Track error history
140 self.relax_err_history = [self.relax_err_history, self.maxitererr(it)];
141 wnd = self.options.config.relax_history;
142 if length(self.relax_err_history) > wnd
143 self.relax_err_history = self.relax_err_history(end-wnd+1:end);
146 if length(self.relax_err_history) >= 3
147 % Detect oscillation by counting sign changes in error differences
148 err = self.relax_err_history;
149 diff_err = diff(err);
150 sign_changes = sum(diff_err(1:end-1) .* diff_err(2:end) < 0);
152 if strcmpi(relax_mode,
'auto') && self.relax_omega == 1.0
153 % For
'auto' mode: enable relaxation when oscillation detected
154 if sign_changes >= length(diff_err) * 0.5
155 self.relax_omega = self.options.config.relax_factor;
156 % Debug output removed
157 if self.options.verbose
158 % line_printf(sprintf(
' [enabling relaxation, omega=%.2f]', self.relax_omega));
161 elseif strcmpi(relax_mode,
'adaptive')
162 % For
'adaptive' mode: adjust omega based on error trajectory
163 if sign_changes >= length(diff_err) * 0.5
164 % Oscillating - reduce omega
165 self.relax_omega = max(self.options.config.relax_min, self.relax_omega * 0.8);
166 % Debug output removed
167 if self.options.verbose
168 % line_printf(sprintf(
' [omega=%.2f]', self.relax_omega));
170 elseif sign_changes == 0 && self.maxitererr(it) < self.maxitererr(it-1)
171 % Monotonically decreasing - can increase omega slightly
172 self.relax_omega = min(1.0, self.relax_omega * 1.05);
179%% Check convergence. Do not allow to converge in less than 2 iterations.
180if it==0 && self.options.verbose
181 % Debug output removed
182elseif it>iter_min && self.maxitererr(it) < self.options.iter_tol && self.maxitererr(it-1) < self.options.iter_tol && self.maxitererr(it-2) < self.options.iter_tol
183 if ~self.hasconverged %
if potential convergence has just been detected
184 %
do a hard reset of every layer to check that
this is really the fixed point
186 self.ensemble{e}.reset();
188 if self.options.verbose
189 % Debug output removed
191 self.hasconverged =
true; %
if it passes the change again next time then complete
193 if self.options.verbose
194 if self.solvers{end}.options.verbose
195 % Debug output removed
201 self.hasconverged =
false;