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