Stroika Library 3.0d25
 
Loading...
Searching...
No Matches
DownhillSimplexMinimization.inl
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
9
10// Comment this in to turn on aggressive noisy DbgTrace in this module
11//#define Stroika_Foundation_Math_Optimization_DownhillSimplexMinimization_USE_NOISY_TRACE_IN_THIS_MODULE_ 1
12
13namespace Stroika::Foundation::Math::Optimization::DownhillSimplexMinimization {
14
15 /*
16 ********************************************************************************
17 ****************** DownhillSimplexMinimization::Options ************************
18 ********************************************************************************
19 */
20 template <typename FLOAT_TYPE>
21 Characters::String Options<FLOAT_TYPE>::ToString () const
22 {
23 Characters::StringBuilder sb;
24 sb << "{"sv;
25 sb << "maxIterations: "sv << fMaxIterations << ", "sv;
26 sb << "noImprovementThreshold: "sv << fNoImprovementThreshold;
27 sb << "}"sv;
28 return sb;
29 }
30
31 /*
32 ********************************************************************************
33 ****************** DownhillSimplexMinimization::Results ************************
34 ********************************************************************************
35 */
36 template <typename FLOAT_TYPE>
37 Characters::String Results<FLOAT_TYPE>::ToString () const
38 {
39 Characters::StringBuilder sb;
40 sb << "{"sv;
41 sb << "Optimized-Parameters: "sv << fOptimizedParameters << ","sv;
42 sb << "Score: "sv << fScore << ","sv;
43 sb << "Iteration-Count: "sv << fIterationCount;
44 sb << "}"sv;
45 return sb;
46 }
47
48 /*
49 ********************************************************************************
50 ******************** DownhillSimplexMinimization::Run **************************
51 ********************************************************************************
52 */
53 namespace PRIVATE_ {
54 using Containers::Sequence;
55 using namespace LinearAlgebra;
56
57 // Translated by hand from https://github.com/fchollet/nelder-mead/blob/master/nelder_mead.py - LGP 2017-02-20
58 template <typename FLOAT_TYPE>
59 Results<FLOAT_TYPE> nelder_mead_by_fchollet (const TargetFunction<FLOAT_TYPE>& f, const LinearAlgebra::Vector<FLOAT_TYPE>& x_start,
60 FLOAT_TYPE step = 0.1, FLOAT_TYPE no_improve_thr = 10e-6,
61 unsigned int no_improv_break = 10, unsigned int max_iter = 0, FLOAT_TYPE alpha = 1,
62 FLOAT_TYPE gamma = 2, FLOAT_TYPE rho = -0.5, FLOAT_TYPE sigma = 0.5)
63 {
64 // init
65 size_t dim = x_start.GetDimension ();
66 FLOAT_TYPE prev_best = f (x_start.GetItems ());
67 unsigned int no_improv = 0;
68 struct PartialResultType_ {
69 Sequence<FLOAT_TYPE> fResults;
70 FLOAT_TYPE fScore;
71 };
72
73 // @todo - fix - temporarily must use vector due to need to call sort which requires random access iterators
74 vector<PartialResultType_> res{PartialResultType_{x_start.GetItems (), prev_best}};
75 for (size_t i = 0; i != dim; ++i) {
76 Vector<FLOAT_TYPE> x = x_start;
77 x[i] += step;
78 FLOAT_TYPE score{f (x.GetItems ())};
79 res.push_back (PartialResultType_{x.GetItems (), score});
80 }
81
82 // Simplex iteration
83 unsigned int iters = 0;
84 while (true) {
85 sort (res.begin (), res.end (), [] (auto l, auto r) { return l.fScore < r.fScore; });
86 FLOAT_TYPE best = res[0].fScore;
87
88 // break after max_iter
89 if (max_iter and iters >= max_iter) {
90 return Results<FLOAT_TYPE>{res[0].fResults, res[0].fScore, iters};
91 }
92 iters += 1;
93
94#if Stroika_Foundation_Math_Optimization_DownhillSimplexMinimization_USE_NOISY_TRACE_IN_THIS_MODULE_
95 DbgTrace ("...best so far (iteration {}): {}"_f, iters, best);
96#endif
97 // break after no_improv_break iterations with no improvement
98 if (best < prev_best - no_improve_thr) {
99 no_improv = 0;
100 prev_best = best;
101 }
102 else {
103 no_improv += 1;
104 }
105 if (no_improv >= no_improv_break) {
106 return Results<FLOAT_TYPE>{res[0].fResults, res[0].fScore, iters};
107 }
108
109 // Centroid
110 Vector<FLOAT_TYPE> x0{dim, 0.0};
111 {
112 auto removeLast = [] (decltype (res) v) {
113 decltype (v) tmp;
114 for (auto i = v.begin (); i != v.end () and i + 1 != v.end (); ++i) {
115 tmp.push_back (*i);
116 }
117 return tmp;
118 };
119 for (const PartialResultType_& tup : removeLast (res)) {
120 for (size_t i = 0; i < x0.GetDimension (); ++i) {
121 FLOAT_TYPE c = Sequence<FLOAT_TYPE>{tup.fResults}[i];
122 x0[i] += c / (res.size () - 1);
123 }
124 }
125 }
126
127 // Reflection
128 Vector<FLOAT_TYPE> xr = x0 + alpha * (x0 - Vector<FLOAT_TYPE>{res[res.size () - 1].fResults});
129 FLOAT_TYPE rscore = f (xr.GetItems ());
130 {
131 if (res[0].fScore <= rscore and rscore < res[res.size () - 2].fScore) {
132 res[res.size () - 1] = PartialResultType_{xr.GetItems (), rscore};
133 continue;
134 }
135 }
136
137 // Expansion
138 {
139 if (rscore < res[0].fScore) {
140 Vector<FLOAT_TYPE> xe = x0 + gamma * (x0 - Vector<FLOAT_TYPE>{res[res.size () - 1].fResults});
141 FLOAT_TYPE escore = f (xe.GetItems ());
142 if (escore < rscore) {
143 res[res.size () - 1] = PartialResultType_{xe.GetItems (), escore};
144 }
145 else {
146 res[res.size () - 1] = PartialResultType_{xr.GetItems (), rscore};
147 }
148 continue;
149 }
150 }
151
152 // Contraction
153 {
154 Vector<FLOAT_TYPE> xc = x0 + rho * (x0 - Vector<FLOAT_TYPE>{res[res.size () - 1].fResults});
155 FLOAT_TYPE cscore = f (xc.GetItems ());
156 if (cscore < res[res.size () - 1].fScore) {
157 res[res.size () - 1] = PartialResultType_{xc.GetItems (), cscore};
158 }
159 continue;
160 }
161
162 // Reduction
163 {
164 Vector<FLOAT_TYPE> x1 = res[0].fResults;
165 vector<PartialResultType_> nres;
166 for (const PartialResultType_& tup : res) {
167 Vector<FLOAT_TYPE> redx = x1 + sigma * (Vector<FLOAT_TYPE>{tup.fResults} - x1);
168 FLOAT_TYPE score = f (redx.GetItems ());
169 res.push_back (PartialResultType_{redx.GetItems (), score});
170 }
171 res = nres;
172 }
173 }
175 return Results<FLOAT_TYPE>{};
176 };
177 }
178 template <typename FLOAT_TYPE>
179 Results<FLOAT_TYPE> Run (const TargetFunction<FLOAT_TYPE>& function2Minimize, const Sequence<FLOAT_TYPE>& initialValues,
180 const Options<FLOAT_TYPE>& options)
181 {
182#if Stroika_Foundation_Math_Optimization_DownhillSimplexMinimization_USE_NOISY_TRACE_IN_THIS_MODULE_
183 Debug::TraceContextBumper ctx{"Optimization::DownhillSimplexMinimization::Run", "initialValues={}, options={}"_f, initialValues, options};
184#endif
185 Results<FLOAT_TYPE> results{};
186 FLOAT_TYPE step = 0.1;
187 FLOAT_TYPE no_improve_thr = options.fNoImprovementThreshold;
188 unsigned int no_improv_break = 10;
189 unsigned int max_iter = options.fMaxIterations;
190 results = PRIVATE_::nelder_mead_by_fchollet<FLOAT_TYPE> (function2Minimize, initialValues, step, no_improve_thr, no_improv_break, max_iter);
191#if Stroika_Foundation_Math_Optimization_DownhillSimplexMinimization_USE_NOISY_TRACE_IN_THIS_MODULE_
192 DbgTrace ("returns: {}"_f, results);
193#endif
194 return results;
195 }
196
197}
#define AssertNotReached()
Definition Assertions.h:358
Results< FLOAT_TYPE > Run(const TargetFunction< FLOAT_TYPE > &function2Minimize, const Sequence< FLOAT_TYPE > &initialValues, const Options< FLOAT_TYPE > &options=Options< FLOAT_TYPE >{})
Downhill Simplex Minimization, AKA Nelder-Mead algorithm, to compute minimization.
#define DbgTrace
Definition Trace.h:327
A generalization of a vector: a container whose elements are keyed by the natural numbers.