/src/quantlib/ql/math/optimization/simplex.cpp
Line | Count | Source |
1 | | /* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | |
3 | | /* |
4 | | Copyright (C) 2001, 2002, 2003 Sadruddin Rejeb |
5 | | Copyright (C) 2006 Ferdinando Ametrano |
6 | | Copyright (C) 2007 Marco Bianchetti |
7 | | Copyright (C) 2007 François du Vignaud |
8 | | |
9 | | This file is part of QuantLib, a free-software/open-source library |
10 | | for financial quantitative analysts and developers - http://quantlib.org/ |
11 | | |
12 | | QuantLib is free software: you can redistribute it and/or modify it |
13 | | under the terms of the QuantLib license. You should have received a |
14 | | copy of the license along with this program; if not, please email |
15 | | <quantlib-dev@lists.sf.net>. The license is also available online at |
16 | | <https://www.quantlib.org/license.shtml>. |
17 | | |
18 | | This program is distributed in the hope that it will be useful, but WITHOUT |
19 | | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
20 | | FOR A PARTICULAR PURPOSE. See the license for more details. |
21 | | */ |
22 | | |
23 | | /* The implementation of the algorithm was highly inspired by |
24 | | * "Numerical Recipes in C", 2nd edition, Press, Teukolsky, Vetterling, |
25 | | * Flannery, chapter 10. |
26 | | * Modified may 2007: end criteria set on x instead on fx, |
27 | | * inspired by bad behaviour found with test function fx=x*x+x+1, |
28 | | * xStart = -100, lambda = 1.0, ftol = 1.e-16 |
29 | | * (it reports x=0 as the minimum!) |
30 | | * and by GSL implementation, v. 1.9 (http://www.gnu.org/software/gsl/) |
31 | | */ |
32 | | |
33 | | #include <ql/math/optimization/simplex.hpp> |
34 | | #include <ql/math/optimization/constraint.hpp> |
35 | | |
36 | | namespace QuantLib { |
37 | | |
38 | | namespace { |
39 | | // Computes the size of the simplex |
40 | 0 | Real computeSimplexSize (const std::vector<Array>& vertices) { |
41 | 0 | Array center(vertices.front().size(),0); |
42 | 0 | for (const auto& vertice : vertices) |
43 | 0 | center += vertice; |
44 | 0 | center *=1/Real(vertices.size()); |
45 | 0 | Real result = 0; |
46 | 0 | for (const auto& vertice : vertices) { |
47 | 0 | Array temp = vertice - center; |
48 | 0 | result += Norm2(temp); |
49 | 0 | } |
50 | 0 | return result/Real(vertices.size()); |
51 | 0 | } |
52 | | } |
53 | | |
54 | | Real Simplex::extrapolate(Problem& P, |
55 | | Size iHighest, |
56 | 0 | Real &factor) const { |
57 | |
|
58 | 0 | Array pTry; |
59 | 0 | do { |
60 | 0 | Size dimensions = values_.size() - 1; |
61 | 0 | Real factor1 = (1.0 - factor)/dimensions; |
62 | 0 | Real factor2 = factor1 - factor; |
63 | 0 | pTry = sum_*factor1 - vertices_[iHighest]*factor2; |
64 | 0 | factor *= 0.5; |
65 | 0 | } while (!P.constraint().test(pTry) && std::fabs(factor) > QL_EPSILON); |
66 | 0 | if (std::fabs(factor) <= QL_EPSILON) { |
67 | 0 | return values_[iHighest]; |
68 | 0 | } |
69 | 0 | factor *= 2.0; |
70 | 0 | Real vTry = P.value(pTry); |
71 | 0 | if (vTry < values_[iHighest]) { |
72 | 0 | values_[iHighest] = vTry; |
73 | 0 | sum_ += pTry - vertices_[iHighest]; |
74 | 0 | vertices_[iHighest] = pTry; |
75 | 0 | } |
76 | 0 | return vTry; |
77 | |
|
78 | 0 | } |
79 | | |
80 | | |
81 | | EndCriteria::Type Simplex::minimize(Problem& P, |
82 | 0 | const EndCriteria& endCriteria) { |
83 | | // set up of the problem |
84 | | //Real ftol = endCriteria.functionEpsilon(); // end criteria on f(x) (see Numerical Recipes in C++, p.410) |
85 | 0 | Real xtol = endCriteria.rootEpsilon(); // end criteria on x (see GSL v. 1.9, http://www.gnu.org/software/gsl/) |
86 | 0 | Size maxStationaryStateIterations_ |
87 | 0 | = endCriteria.maxStationaryStateIterations(); |
88 | 0 | EndCriteria::Type ecType = EndCriteria::None; |
89 | 0 | P.reset(); |
90 | |
|
91 | 0 | Array x_ = P.currentValue(); |
92 | 0 | if (!P.constraint().test(x_)) |
93 | 0 | QL_FAIL("Initial guess " << x_ << " is not in the feasible region."); |
94 | | |
95 | 0 | Integer iterationNumber_=0; |
96 | | |
97 | | // Initialize vertices of the simplex |
98 | 0 | Size n = x_.size(); |
99 | 0 | vertices_ = std::vector<Array>(n+1, x_); |
100 | 0 | for (Size i=0; i<n; ++i) { |
101 | 0 | Array direction(n, 0.0); |
102 | 0 | direction[i] = 1.0; |
103 | 0 | P.constraint().update(vertices_[i+1], direction, lambda_); |
104 | 0 | } |
105 | | // Initialize function values at the vertices of the simplex |
106 | 0 | values_ = Array(n+1, 0.0); |
107 | 0 | for (Size i=0; i<=n; ++i) |
108 | 0 | values_[i] = P.value(vertices_[i]); |
109 | | // Loop looking for minimum |
110 | 0 | do { |
111 | 0 | sum_ = Array(n, 0.0); |
112 | 0 | Size i; |
113 | 0 | for (i=0; i<=n; i++) |
114 | 0 | sum_ += vertices_[i]; |
115 | | // Determine the best (iLowest), worst (iHighest) |
116 | | // and 2nd worst (iNextHighest) vertices |
117 | 0 | Size iLowest = 0; |
118 | 0 | Size iHighest, iNextHighest; |
119 | 0 | if (values_[0]<values_[1]) { |
120 | 0 | iHighest = 1; |
121 | 0 | iNextHighest = 0; |
122 | 0 | } else { |
123 | 0 | iHighest = 0; |
124 | 0 | iNextHighest = 1; |
125 | 0 | } |
126 | 0 | for (i=1;i<=n; i++) { |
127 | 0 | if (values_[i]>values_[iHighest]) { |
128 | 0 | iNextHighest = iHighest; |
129 | 0 | iHighest = i; |
130 | 0 | } else { |
131 | 0 | if ((values_[i]>values_[iNextHighest]) && i!=iHighest) |
132 | 0 | iNextHighest = i; |
133 | 0 | } |
134 | 0 | if (values_[i]<values_[iLowest]) |
135 | 0 | iLowest = i; |
136 | 0 | } |
137 | | // Now compute accuracy, update iteration number and check end criteria |
138 | | //// Numerical Recipes exit strategy on fx (see NR in C++, p.410) |
139 | | //Real low = values_[iLowest]; |
140 | | //Real high = values_[iHighest]; |
141 | | //Real rtol = 2.0*std::fabs(high - low)/ |
142 | | // (std::fabs(high) + std::fabs(low) + QL_EPSILON); |
143 | | //++iterationNumber_; |
144 | | //if (rtol < ftol || |
145 | | // endCriteria.checkMaxIterations(iterationNumber_, ecType)) { |
146 | | // GSL exit strategy on x (see GSL v. 1.9, http://www.gnu.org/software/gsl |
147 | 0 | Real simplexSize = computeSimplexSize(vertices_); |
148 | 0 | ++iterationNumber_; |
149 | 0 | if (simplexSize < xtol || |
150 | 0 | endCriteria.checkMaxIterations(iterationNumber_, ecType)) { |
151 | 0 | endCriteria.checkStationaryPoint(0.0, 0.0, |
152 | 0 | maxStationaryStateIterations_, ecType); |
153 | 0 | endCriteria.checkMaxIterations(iterationNumber_, ecType); |
154 | 0 | x_ = vertices_[iLowest]; |
155 | 0 | Real low = values_[iLowest]; |
156 | 0 | P.setFunctionValue(low); |
157 | 0 | P.setCurrentValue(x_); |
158 | 0 | return ecType; |
159 | 0 | } |
160 | | // If end criteria is not met, continue |
161 | 0 | Real factor = -1.0; |
162 | 0 | Real vTry = extrapolate(P, iHighest, factor); |
163 | 0 | if ((vTry <= values_[iLowest]) && (factor == -1.0)) { |
164 | 0 | factor = 2.0; |
165 | 0 | extrapolate(P, iHighest, factor); |
166 | 0 | } else if (std::fabs(factor) > QL_EPSILON) { |
167 | 0 | if (vTry >= values_[iNextHighest]) { |
168 | 0 | Real vSave = values_[iHighest]; |
169 | 0 | factor = 0.5; |
170 | 0 | vTry = extrapolate(P, iHighest, factor); |
171 | 0 | if (vTry >= vSave && std::fabs(factor) > QL_EPSILON) { |
172 | 0 | for (Size i=0; i<=n; i++) { |
173 | 0 | if (i!=iLowest) { |
174 | 0 | vertices_[i] = |
175 | 0 | 0.5*(vertices_[i] + vertices_[iLowest]); |
176 | 0 | values_[i] = P.value(vertices_[i]); |
177 | 0 | } |
178 | 0 | } |
179 | 0 | } |
180 | 0 | } |
181 | 0 | } |
182 | | // If can't extrapolate given the constraints, exit |
183 | 0 | if (std::fabs(factor) <= QL_EPSILON) { |
184 | 0 | x_ = vertices_[iLowest]; |
185 | 0 | Real low = values_[iLowest]; |
186 | 0 | P.setFunctionValue(low); |
187 | 0 | P.setCurrentValue(x_); |
188 | 0 | return EndCriteria::StationaryFunctionValue; |
189 | 0 | } |
190 | 0 | } while (true); |
191 | 0 | QL_FAIL("optimization failed: unexpected behaviour"); |
192 | 0 | } |
193 | | } |