Newer
Older
/*! \file Simplex.cpp
\brief Contains definitions of Simplex class that can be used for Nelder-Mead simplex minimisation.
\author Jesper Andersson
\version 1.0b, Oct., 2013.
*/
// Contains definitions of Simplex class that can
// be used for Nelder-Mead simplex minimisation.
//
// Simplex.cpp
//
// Jesper Andersson, FMRIB Image Analysis Group
//
// Copyright (C) 2013 University of Oxford
//
#include <iostream>
#include <cfloat>
#include <cmath>
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
#include <string>
#include <vector>
#include "newmat.h"
#include "miscmaths.h"
#include "nonlin.h"
#include "Simplex.h"
using namespace MISCMATHS;
/****************************************************************//**
*
* Constructs a Simplex object given a vector of starting guesses
* and a cost-function object derived from the NonlinCF base class.
* The simplex is created by placing n points at a distance 0.1p
* from the nx1 start guess. If p is zero along any dimension the
* corresponding point is placed at unity distance instead.
* \param p Starting guess for the parameters.
* \param cf Cost-function object of a class derived from the virtual
* NonlinCF base class.
*
********************************************************************/
Simplex::Simplex(const NEWMAT::ColumnVector& p,
const MISCMATHS::NonlinCF& cf)
: _cf(cf), _sp(p)
{
NEWMAT::ColumnVector l(_sp.Nrows());
for (int i=0; i<l.Nrows(); i++) {
l(i+1) = (p(i+1)) ? 0.1*p(i+1) : 1.0;
}
setup_simplex(l);
UpdateRankIndicies();
}
/****************************************************************//**
*
* Constructs a Simplex object given a vector of starting guesses,
* a cost-function object derived from the NonlinCF base class and
* a vector of perturbations used to build the simplex.
* \param p Starting guess for the parameters.
* \param cf Cost-function object of a class derived from the virtual
* NonlinCF base class.
* \param l Perturbations used to create the nodes of the simplex.
* On initialisation the ith point of the simplex will be
* smplx[i] = p; smplx[i](i) += l(i);
*
********************************************************************/
Simplex::Simplex(const NEWMAT::ColumnVector& p,
const MISCMATHS::NonlinCF& cf,
const NEWMAT::ColumnVector& l)
: _cf(cf), _sp(p)
{
if (l.Nrows() != _sp.Nrows()) throw ;
setup_simplex(l);
UpdateRankIndicies();
}
/****************************************************************//**
*
* Minimises the simplex, i.e. it will find the set of parameters
* that minimmises the NonlinCF cost-function that the Simplex
* object was constructed with.
* Suggested use:
* \code
* Simplex my_simplex(my_guess_par,my_cost_func);
* if (my_simplex(my_tol,1000)) {
* ColumnVector optimal_par = my_simplex.BestPar();
* }
* else { cout << "bugger" << endl; }
* \endcode
*
* \param ftol Fractional cost-function tolerance for convergence.
* \param miter Maximum allowed number of iterations.
*
********************************************************************/
bool Simplex::Minimise(double ftol,
unsigned int miter)
{
UpdateRankIndicies(); // Make sure it is ready for use
for (unsigned int i=0; i<miter; i++) {
if (HasConverged(ftol)) return(true); // Check for convergence
double newf = Reflect(); // Attempt reflexion
// Extend into an expansion if reflexion very successful
if (newf <= BestFuncVal()) {
Expand(); // Attempt expansion
}
else if (newf >= SecondWorstFuncVal()) {
double worst_fval = WorstFuncVal();
newf = Contract(); // Do a contraction towards plane of "better" points
if (newf >= worst_fval) { // Didn't work. Contract towards best point
MultiContract();
}
}
UpdateRankIndicies();
}
return(false);
}
double Simplex::Reflect()
{
calculate_reflexion_point(_wrsti); // Updates _rp
NEWMAT::ColumnVector newp = 2.0*_rp - _smx[_wrsti];
double newf = _cf.cf(newp);
if (newf < _fv[_wrsti]) {
_smx[_wrsti] = newp;
_fv[_wrsti] = newf;
}
return(newf);
}
double Simplex::Expand()
{
NEWMAT::ColumnVector newp = 2.0*_smx[_wrsti] - _rp;
double newf = _cf.cf(newp);
if (newf < _fv[_wrsti]) {
_smx[_wrsti] = newp;
_fv[_wrsti] = newf;
}
return(newf);
}
double Simplex::Contract()
{
NEWMAT::ColumnVector newp = 0.5 * (_smx[_wrsti] + _rp);
double newf = _cf.cf(newp);
if (newf < _fv[_wrsti]) {
_smx[_wrsti] = newp;
_fv[_wrsti] = newf;
}
return(newf);
}
void Simplex::MultiContract()
{
for (unsigned int i=0; i<_smx.size(); i++) {
if (i != _bsti) {
_smx[i] = 0.5 * (_smx[i] + _smx[_bsti]);
_fv[i] = _cf.cf(_smx[i]);
}
}
return;
}
void Simplex::UpdateRankIndicies()
{
double minv = std::numeric_limits<double>::max();

Jesper Andersson
committed
double maxv = -std::numeric_limits<double>::max();
for (unsigned int i=0; i<_fv.size(); i++) {
if (_fv[i] < minv) { minv = _fv[i]; _bsti = i; }
if (_fv[i] > maxv) { maxv = _fv[i]; _wrsti = i; }
}

Jesper Andersson
committed
maxv = -std::numeric_limits<double>::max();
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
for (unsigned int i=0; i<_fv.size(); i++) {
if (i != _wrsti) {
if (_fv[i] > maxv) { maxv = _fv[i]; _nwsti = i; }
}
}
return;
}
void Simplex::setup_simplex(const NEWMAT::ColumnVector& l)
{
_smx.resize(_sp.Nrows()+1);
_fv.resize(_smx.size());
_smx[0] = _sp;
_fv[0] = _cf.cf(_smx[0]);
for (int i=1; i<=_sp.Nrows(); i++) {
_smx[i] = _sp;
_smx[i](i) += l(i);
_fv[i] = _cf.cf(_smx[i]);
}
return;
}
void Simplex::calculate_reflexion_point(unsigned int ii)
{
if (_rp.Nrows() != _sp.Nrows()) _rp.ReSize(_sp.Nrows());
_rp = 0.0;
for (unsigned int i=0; i<_smx.size(); i++) {
if (i != ii) _rp += _smx[i];
}
_rp /= static_cast<double>(_rp.Nrows());
}