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