1 /*
2 * Medical Image Registration ToolKit (MIRTK)
3 *
4 * Copyright 2013-2015 Imperial College London
5 * Copyright 2013-2015 Andreas Schuh
6 *
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
18 */
19
20 #include "mirtk/LocalOptimizer.h"
21 #include "mirtk/StoppingCriterion.h"
22
23 #include "mirtk/Math.h"
24 #include "mirtk/Array.h"
25 #include "mirtk/Memory.h"
26
27
28 namespace mirtk {
29
30
31 // =============================================================================
32 // Factory method
33 // =============================================================================
34
35 // Define singleton factory class for instantiation of local optimizers
36 mirtkDefineObjectFactory(OptimizationMethod, LocalOptimizer);
37
38 // -----------------------------------------------------------------------------
Factory()39 LocalOptimizer::FactoryType &LocalOptimizer::Factory()
40 {
41 return LocalOptimizerFactory::Instance();
42 }
43
44 // -----------------------------------------------------------------------------
New(enum OptimizationMethod type,ObjectiveFunction * f)45 LocalOptimizer *LocalOptimizer::New(enum OptimizationMethod type, ObjectiveFunction *f)
46 {
47 LocalOptimizer *optimizer = Factory().New(type);
48 if (!optimizer) {
49 cerr << "LocalOptimizer::New: Unknown optimizer or optimizer not available: "
50 << ToString(type) << " (code " << type << ")" << endl;
51 exit(1);
52 }
53 optimizer->Function(f);
54 return optimizer;
55 }
56
57 // =============================================================================
58 // Line fitting
59 // =============================================================================
60
61 // -----------------------------------------------------------------------------
62 /// Compute slope of least squares fit of line to last n objective function values
63 ///
64 /// \see https://www.che.udel.edu/pdf/FittingData.pdf
65 /// \see https://en.wikipedia.org/wiki/1_%2B_2_%2B_3_%2B_4_%2B_%E2%8B%AF
66 /// \see https://proofwiki.org/wiki/Sum_of_Sequence_of_Squares
SlopeOfLeastSquaresFit(const Deque<double> & values)67 double SlopeOfLeastSquaresFit(const Deque<double> &values)
68 {
69 // sum_x1 divided by n as a slight modified to reduce no. of operations,
70 // i.e., the other terms are divided by n as well by dropping one factor n
71 const int n = static_cast<int>(values.size());
72 if (n < 2) return NaN;
73 if (n == 2) return values.back() - values.front();
74 double sum_x1 = double(n + 1) / 2.; // sum of x / n
75 double sum_x2 = n * double(n + 1) * (2. * n + 1) / 6.; // sum of x^2
76 double sum_y1 = 0.;
77 double sum_xy = 0.;
78 double x = 1.;
79 for (auto y : values) {
80 sum_y1 += y;
81 sum_xy += x * y;
82 x += 1.;
83 }
84 return (sum_xy - sum_x1 * sum_y1) / (sum_x2 - n * sum_x1 * sum_x1);
85 }
86
87 // =============================================================================
88 // Construction/Destruction
89 // =============================================================================
90
91 // -----------------------------------------------------------------------------
LocalOptimizer(ObjectiveFunction * f)92 LocalOptimizer::LocalOptimizer(ObjectiveFunction *f)
93 :
94 _Function(f),
95 _NumberOfSteps(100),
96 _Epsilon(1e-4),
97 _Delta(1e-12),
98 _NumberOfLastValues(2),
99 _Converged(false)
100 {
101 }
102
103 // -----------------------------------------------------------------------------
CopyAttributes(const LocalOptimizer & other)104 void LocalOptimizer::CopyAttributes(const LocalOptimizer &other)
105 {
106 _Function = other._Function;
107 _NumberOfSteps = other._NumberOfSteps;
108 _Epsilon = other._Epsilon;
109 _Delta = other._Delta;
110 _LastValues = other._LastValues;
111 _NumberOfLastValues = other._NumberOfLastValues;
112 _Converged = other._Converged;
113
114 ClearStoppingCriteria();
115 for (int i = 0; i < other.NumberOfStoppingCriteria(); ++i) {
116 this->AddStoppingCriterion(other.StoppingCriterion(i)->New());
117 }
118 }
119
120 // -----------------------------------------------------------------------------
LocalOptimizer(const LocalOptimizer & other)121 LocalOptimizer::LocalOptimizer(const LocalOptimizer &other)
122 :
123 Observable(other)
124 {
125 CopyAttributes(other);
126 }
127
128 // -----------------------------------------------------------------------------
operator =(const LocalOptimizer & other)129 LocalOptimizer &LocalOptimizer::operator =(const LocalOptimizer &other)
130 {
131 if (this != &other) {
132 Observable::operator =(other);
133 CopyAttributes(other);
134 }
135 return *this;
136 }
137
138 // -----------------------------------------------------------------------------
~LocalOptimizer()139 LocalOptimizer::~LocalOptimizer()
140 {
141 ClearStoppingCriteria();
142 }
143
144 // -----------------------------------------------------------------------------
Initialize()145 void LocalOptimizer::Initialize()
146 {
147 // Check objective function
148 if (!_Function) {
149 cerr << this->NameOfClass() << "::Initialize: Objective function not set" << endl;
150 exit(1);
151 }
152 if (_Function->NumberOfDOFs() <= 0) {
153 cerr << this->NameOfClass() << "::Initialize: Objective function has no free parameters (DoFs)" << endl;
154 exit(1);
155 }
156
157 // Initialize container for last objective function values
158 _LastValues.clear();
159 _LastValuesSlope = NaN;
160 if (_NumberOfLastValues < 2) {
161 // Need at least the previous and current function value for _Epsilon stopping criterion
162 _NumberOfLastValues = 2;
163 }
164
165 // Initialize stopping criteria
166 for (size_t n = 0; n < _StoppingCriteria.size(); ++n) {
167 class StoppingCriterion *criterion = _StoppingCriteria[n];
168 criterion->Optimizer(this);
169 criterion->Function(_Function);
170 criterion->Initialize();
171 }
172 _Converged = false;
173 }
174
175 // =============================================================================
176 // Stopping criteria
177 // =============================================================================
178
179 // -----------------------------------------------------------------------------
NumberOfStoppingCriteria() const180 int LocalOptimizer::NumberOfStoppingCriteria() const
181 {
182 return static_cast<int>(_StoppingCriteria.size());
183 }
184
185 // -----------------------------------------------------------------------------
AddStoppingCriterion(class StoppingCriterion * criterion)186 void LocalOptimizer::AddStoppingCriterion(class StoppingCriterion *criterion)
187 {
188 Array<class StoppingCriterion *>::iterator it;
189 for (it = _StoppingCriteria.begin(); it != _StoppingCriteria.end(); ++it) {
190 if (*it == criterion) return;
191 }
192 _StoppingCriteria.push_back(criterion);
193 }
194
195 // -----------------------------------------------------------------------------
RemoveStoppingCriterion(class StoppingCriterion * criterion)196 void LocalOptimizer::RemoveStoppingCriterion(class StoppingCriterion *criterion)
197 {
198 Array<class StoppingCriterion *>::iterator it;
199 for (it = _StoppingCriteria.begin(); it != _StoppingCriteria.end(); ++it) {
200 if (*it == criterion) {
201 _StoppingCriteria.erase(it);
202 break;
203 }
204 }
205 }
206
207 // -----------------------------------------------------------------------------
StoppingCriterion(int n)208 StoppingCriterion *LocalOptimizer::StoppingCriterion(int n)
209 {
210 return _StoppingCriteria[n];
211 }
212
213 // -----------------------------------------------------------------------------
StoppingCriterion(int n) const214 const StoppingCriterion *LocalOptimizer::StoppingCriterion(int n) const
215 {
216 return _StoppingCriteria[n];
217 }
218
219 // -----------------------------------------------------------------------------
ClearStoppingCriteria()220 void LocalOptimizer::ClearStoppingCriteria()
221 {
222 for (size_t n = 0; n < _StoppingCriteria.size(); ++n) {
223 Delete(_StoppingCriteria[n]);
224 }
225 _StoppingCriteria.clear();
226 }
227
228 // -----------------------------------------------------------------------------
Converged(int iter,double value,const double * dx)229 bool LocalOptimizer::Converged(int iter, double value, const double *dx)
230 {
231 // Detect numerical problem of objective function implementation
232 if (IsNaN(value)) {
233 cerr << this->NameOfClass() << ": NaN objective function value!" << endl;
234 exit(1);
235 }
236
237 // Update history of last n function values
238 double prev = NaN;
239 if (!_LastValues.empty()) {
240 prev = _LastValues.back();
241 }
242 _LastValues.push_back(value);
243 if (_LastValues.size() > static_cast<size_t>(_NumberOfLastValues)) {
244 _LastValues.pop_front();
245 }
246
247 // Objective function value changed from < inf to inf although the value may either
248 // only be always inf in case of a deformable surface model, or always < inf otherwise.
249 if (!IsNaN(prev) && !IsInf(prev) && IsInf(value)) {
250 return true;
251 }
252
253 // Check convergence towards local extremum of objective function
254 const double epsilon = (_Epsilon < 0. ? abs(_Epsilon * value) : _Epsilon);
255 _LastValuesSlope = SlopeOfLeastSquaresFit(_LastValues);
256 if (abs(_LastValuesSlope) < epsilon) return true;
257
258 // Check minimum change requirement
259 if (_Function->GradientNorm(dx) <= _Delta) return true;
260
261 // Test other stopping criteria
262 const int ncriteria = static_cast<int>(_StoppingCriteria.size());
263 for (int n = 0; n < ncriteria; ++n) {
264 if (StoppingCriterion(n)->Fulfilled(iter, value, dx)) return true;
265 }
266 return false;
267 }
268
269 // =============================================================================
270 // Parameters
271 // =============================================================================
272
273 // -----------------------------------------------------------------------------
Set(const char * name,const char * value)274 bool LocalOptimizer::Set(const char *name, const char *value)
275 {
276 if (strcmp(name, "Maximum no. of iterations") == 0 ||
277 strcmp(name, "Maximum number of iterations") == 0 ||
278 strcmp(name, "Maximum no. of gradient steps") == 0 ||
279 strcmp(name, "Maximum number of gradient steps") == 0 ||
280 strcmp(name, "No. of iterations") == 0 ||
281 strcmp(name, "Number of iterations") == 0 ||
282 strcmp(name, "No. of gradient steps") == 0 ||
283 strcmp(name, "Number of gradient steps") == 0) {
284 return FromString(value, _NumberOfSteps);
285 }
286 if (strcmp(name, "Epsilon") == 0) {
287 return FromString(value, _Epsilon);
288 }
289 if (strcmp(name, "Delta") == 0) {
290 return FromString(value, _Delta) && _Delta >= .0;
291 }
292 if (strcmp(name, "No. of last function values") == 0 ||
293 strcmp(name, "No. of past function values") == 0) {
294 return FromString(value, _NumberOfLastValues);
295 }
296 return false;
297 }
298
299 // -----------------------------------------------------------------------------
Parameter() const300 ParameterList LocalOptimizer::Parameter() const
301 {
302 ParameterList params;
303 Insert(params, "Maximum no. of iterations", _NumberOfSteps);
304 Insert(params, "Epsilon", _Epsilon);
305 Insert(params, "Delta", _Delta);
306 Insert(params, "No. of last function values", _NumberOfLastValues);
307 return params;
308 }
309
310
311 } // namespace mirtk
312