rgsl/types/minimizer.rs
1//
2// A rust binding for the GSL library by Guillaume Gomez (guillaume1.gomez@gmail.com)
3//
4
5/*!
6# One dimensional Minimization
7
8This chapter describes routines for finding minima of arbitrary one-dimensional functions. The
9library provides low level components for a variety of iterative minimizers and convergence tests.
10These can be combined by the user to achieve the desired solution, with full access to the
11intermediate steps of the algorithms. Each class of methods uses the same framework, so that you can
12switch between minimizers at runtime without needing to recompile your program. Each instance of a
13minimizer keeps track of its own state, allowing the minimizers to be used in multi-threaded
14programs.
15
16## Overview
17
18The minimization algorithms begin with a bounded region known to contain a minimum. The region is
19described by a lower bound a and an upper bound b, with an estimate of the location of the minimum
20x.
21
22The value of the function at x must be less than the value of the function at the ends of the
23interval,
24
25f(a) > f(x) < f(b)
26
27This condition guarantees that a minimum is contained somewhere within the interval. On each
28iteration a new point x' is selected using one of the available algorithms. If the new point is a
29better estimate of the minimum, i.e. where f(x') < f(x), then the current estimate of the minimum x
30is updated. The new point also allows the size of the bounded interval to be reduced, by choosing
31the most compact set of points which satisfies the constraint f(a) > f(x) < f(b). The interval is
32reduced until it encloses the true minimum to a desired tolerance. This provides a best estimate of
33the location of the minimum and a rigorous error estimate.
34
35Several bracketing algorithms are available within a single framework. The user provides a
36high-level driver for the algorithm, and the library provides the individual functions necessary for
37each of the steps. There are three main phases of the iteration. The steps are,
38
39 * initialize minimizer state, s, for algorithm T
40 * update s using the iteration T
41 * test s for convergence, and repeat iteration if necessary
42
43The state for the minimizers is held in a gsl_min_fminimizer struct. The updating procedure uses
44only function evaluations (not derivatives).
45
46## Caveats
47
48Note that minimization functions can only search for one minimum at a time. When there are several
49minima in the search area, the first minimum to be found will be returned; however it is difficult
50to predict which of the minima this will be. In most cases, no error will be reported if you try to
51find a minimum in an area where there is more than one.
52
53With all minimization algorithms it can be difficult to determine the location of the minimum to
54full numerical precision. The behavior of the function in the region of the minimum x^* can be
55approximated by a Taylor expansion,
56
57y = f(x^*) + (1/2) f''(x^*) (x - x^*)^2
58
59and the second term of this expansion can be lost when added to the first term at finite precision.
60This magnifies the error in locating x^*, making it proportional to sqrt epsilon (where epsilon
61is the relative accuracy of the floating point numbers). For functions with higher order minima,
62such as x^4, the magnification of the error is correspondingly worse. The best that can be achieved
63is to converge to the limit of numerical accuracy in the function values, rather than the location
64of the minimum itself.
65
66## Providing the function to minimize
67
68You must provide a continuous function of one variable for the minimizers to operate on. In order to
69allow for general parameters the functions are defined by a gsl_function data type (see
70[Providing the function to solve](http://www.gnu.org/software/gsl/manual/html_node/Providing-the-function-to-solve.html#Providing-the-function-to-solve)).
71
72## Iteration
73
74The following functions drive the iteration of each algorithm. Each function performs one iteration
75to update the state of any minimizer of the corresponding type. The same functions work for all
76minimizers so that different methods can be substituted at runtime without modifications to the
77code.
78
79## Stopping Parameters
80
81A minimization procedure should stop when one of the following conditions is true:
82
83 * A minimum has been found to within the user-specified precision.
84 * A user-specified maximum number of iterations has been reached.
85 * An error has occurred.
86
87The handling of these conditions is under user control. The function below allows the user to test
88the precision of the current result.
89
90## Minimization Algorithms
91
92The minimization algorithms described in this section require an initial interval which is
93guaranteed to contain a minimum—if a and b are the endpoints of the interval and x is an estimate of
94the minimum then f(a) > f(x) < f(b). This ensures that the function has at least one minimum
95somewhere in the interval. If a valid initial interval is used then these algorithm cannot fail,
96provided the function is well-behaved.
97!*/
98
99use crate::Value;
100use ffi::FFI;
101use sys;
102
103ffi_wrapper!(
104 Minimizer<'a>,
105 *mut sys::gsl_min_fminimizer,
106 gsl_min_fminimizer_free
107 ;inner_call: sys::gsl_function_struct => sys::gsl_function_struct { function: None, params: std::ptr::null_mut() };
108 ;inner_closure: Option<Box<dyn Fn(f64) -> f64 + 'a>> => None;
109);
110
111impl<'a> Minimizer<'a> {
112 /// This function returns a pointer to a newly allocated instance of a minimizer of type T. For
113 /// example, the following code creates an instance of a golden section minimizer,
114 ///
115 /// ```C
116 /// const gsl_min_fminimizer_type * T
117 /// = gsl_min_fminimizer_goldensection;
118 /// gsl_min_fminimizer * s
119 /// = gsl_min_fminimizer_alloc (T);
120 /// ```
121 ///
122 /// If there is insufficient memory to create the minimizer then the function returns a null
123 /// pointer and the error handler is invoked with an error code of ::NoMem.
124 #[doc(alias = "gsl_min_fminimizer_alloc")]
125 pub fn new(t: MinimizerType) -> Option<Minimizer<'a>> {
126 let ptr = unsafe { sys::gsl_min_fminimizer_alloc(t.unwrap_shared()) };
127
128 if ptr.is_null() {
129 None
130 } else {
131 Some(Self::wrap(ptr))
132 }
133 }
134
135 /// This function sets, or resets, an existing minimizer s to use the function f and the initial
136 /// search interval [x_lower, x_upper], with a guess for the location of the minimum x_minimum.
137 ///
138 /// If the interval given does not contain a minimum, then the function returns an error code of
139 /// `Value::Invalid`.
140 #[doc(alias = "gsl_min_fminimizer_set")]
141 pub fn set<F: Fn(f64) -> f64 + 'a>(
142 &mut self,
143 f: F,
144 x_minimum: f64,
145 x_lower: f64,
146 x_upper: f64,
147 ) -> Result<(), Value> {
148 self.inner_call = wrap_callback!(f, F + 'a);
149 self.inner_closure = Some(Box::new(f));
150
151 let ret = unsafe {
152 sys::gsl_min_fminimizer_set(
153 self.unwrap_unique(),
154 &mut self.inner_call,
155 x_minimum,
156 x_lower,
157 x_upper,
158 )
159 };
160 result_handler!(ret, ())
161 }
162
163 /// This function is equivalent to gsl_min_fminimizer_set but uses the values f_minimum, f_lower
164 /// and f_upper instead of computing f(x_minimum), f(x_lower) and f(x_upper).
165 #[doc(alias = "gsl_min_fminimizer_set_with_values")]
166 pub fn set_with_values<F: Fn(f64) -> f64 + 'a>(
167 &mut self,
168 f: F,
169 x_minimum: f64,
170 f_minimum: f64,
171 x_lower: f64,
172 f_lower: f64,
173 x_upper: f64,
174 f_upper: f64,
175 ) -> Result<(), Value> {
176 self.inner_call = wrap_callback!(f, F + 'a);
177 self.inner_closure = Some(Box::new(f));
178
179 let ret = unsafe {
180 sys::gsl_min_fminimizer_set_with_values(
181 self.unwrap_unique(),
182 &mut self.inner_call,
183 x_minimum,
184 f_minimum,
185 x_lower,
186 f_lower,
187 x_upper,
188 f_upper,
189 )
190 };
191 result_handler!(ret, ())
192 }
193
194 #[doc(alias = "gsl_min_fminimizer_name")]
195 pub fn name(&self) -> Option<String> {
196 let n = unsafe { sys::gsl_min_fminimizer_name(self.unwrap_shared()) };
197 if n.is_null() {
198 return None;
199 }
200 let mut len = 0;
201 loop {
202 if unsafe { *n.offset(len) } == 0 {
203 break;
204 }
205 len += 1;
206 }
207 let slice = unsafe { std::slice::from_raw_parts(n as _, len as _) };
208 std::str::from_utf8(slice).ok().map(|x| x.to_owned())
209 }
210
211 #[doc(alias = "gsl_min_fminimizer_x_minimum")]
212 pub fn x_minimum(&self) -> f64 {
213 unsafe { sys::gsl_min_fminimizer_x_minimum(self.unwrap_shared()) }
214 }
215
216 #[doc(alias = "gsl_min_fminimizer_x_lower")]
217 pub fn x_lower(&self) -> f64 {
218 unsafe { sys::gsl_min_fminimizer_x_lower(self.unwrap_shared()) }
219 }
220
221 #[doc(alias = "gsl_min_fminimizer_x_upper")]
222 pub fn x_upper(&self) -> f64 {
223 unsafe { sys::gsl_min_fminimizer_x_upper(self.unwrap_shared()) }
224 }
225
226 #[doc(alias = "gsl_min_fminimizer_f_minimum")]
227 pub fn f_minimum(&self) -> f64 {
228 unsafe { sys::gsl_min_fminimizer_f_minimum(self.unwrap_shared()) }
229 }
230
231 #[doc(alias = "gsl_min_fminimizer_f_lower")]
232 pub fn f_lower(&self) -> f64 {
233 unsafe { sys::gsl_min_fminimizer_f_lower(self.unwrap_shared()) }
234 }
235
236 #[doc(alias = "gsl_min_fminimizer_f_upper")]
237 pub fn f_upper(&self) -> f64 {
238 unsafe { sys::gsl_min_fminimizer_f_upper(self.unwrap_shared()) }
239 }
240
241 #[doc(alias = "gsl_min_fminimizer_minimum")]
242 pub fn minimum(&self) -> f64 {
243 unsafe { sys::gsl_min_fminimizer_minimum(self.unwrap_shared()) }
244 }
245
246 /// This function performs a single iteration of the minimizer s. If the iteration encounters an
247 /// unexpected problem then an error code will be returned,
248 ///
249 /// `Value::BadFunc`
250 /// the iteration encountered a singular point where the function evaluated to Inf or NaN.
251 ///
252 /// `Value::Failure`
253 /// the algorithm could not improve the current best approximation or bounding interval.
254 ///
255 /// The minimizer maintains a current best estimate of the position of the minimum at all times,
256 /// and the current interval bounding the minimum. This information can be accessed with the
257 /// following auxiliary functions,
258 #[doc(alias = "gsl_min_fminimizer_iterate")]
259 pub fn iterate(&mut self) -> Result<(), Value> {
260 let ret = unsafe { sys::gsl_min_fminimizer_iterate(self.unwrap_unique()) };
261 result_handler!(ret, ())
262 }
263}
264
265ffi_wrapper!(MinimizerType, *const sys::gsl_min_fminimizer_type);
266
267impl MinimizerType {
268 #[doc(alias = "gsl_min_fminimizer_goldensection")]
269 pub fn goldensection() -> Self {
270 ffi_wrap!(gsl_min_fminimizer_goldensection)
271 }
272
273 #[doc(alias = "gsl_min_fminimizer_brent")]
274 pub fn brent() -> Self {
275 ffi_wrap!(gsl_min_fminimizer_brent)
276 }
277
278 #[doc(alias = "gsl_min_fminimizer_quad_golden")]
279 pub fn quad_golden() -> Self {
280 ffi_wrap!(gsl_min_fminimizer_quad_golden)
281 }
282}
283
284#[cfg(any(test, doctest))]
285mod test {
286 /// This doc block will be used to ensure that the closure can't be set everywhere!
287 ///
288 /// ```compile_fail
289 /// use rgsl::*;
290 /// use rgsl::minimizer::test_interval;
291 ///
292 /// fn set(min: &mut Minimizer) {
293 /// let y = "lalal".to_owned();
294 /// min.set(|x| {
295 /// println!("==> {:?}", y);
296 /// x * x - 5.
297 /// }, 1.0, -5.0, 5.0);
298 /// }
299 ///
300 /// let mut min = Minimizer::new(MinimizerType::brent()).unwrap();
301 /// set(&mut min);
302 /// let status = min.iterate();
303 /// ```
304 ///
305 /// Same but a working version:
306 ///
307 /// ```
308 /// use rgsl::*;
309 /// use rgsl::minimizer::test_interval;
310 ///
311 /// fn set(min: &mut Minimizer) {
312 /// min.set(|x| x * x - 5., 1.0, -5.0, 5.0);
313 /// }
314 ///
315 /// let mut min = Minimizer::new(MinimizerType::brent()).unwrap();
316 /// set(&mut min);
317 /// let status = min.iterate();
318 /// ```
319 use super::*;
320 use minimizer::test_interval;
321
322 fn quadratic_test_fn(x: f64) -> f64 {
323 x.powf(2.0) - 5.0
324 }
325
326 #[test]
327 fn test_min() {
328 let mut min = Minimizer::new(MinimizerType::brent()).unwrap();
329 min.set(quadratic_test_fn, 1.0, -5.0, 5.0).unwrap();
330
331 let max_iter = 5_usize;
332 let eps_abs = 0.0001;
333 let eps_rel = 0.0000001;
334
335 let mut status = Value::Continue;
336 let mut iter = 0_usize;
337
338 while matches!(status, Value::Continue) && iter < max_iter {
339 // iterate for next value
340 min.iterate().unwrap(); // fails here w/ segfault
341
342 // test for convergence
343 let r = min.minimum();
344 let x_lo = min.x_lower();
345 let x_hi = min.x_upper();
346
347 status = test_interval(x_lo, x_hi, eps_abs, eps_rel);
348
349 // check if iteration succeeded
350 if matches!(status, Value::Success) {
351 println!("Converged");
352 }
353
354 // print current iteration
355 println!("{} [{}, {}] {} {}", iter, x_lo, x_hi, r, x_hi - x_lo);
356
357 iter += 1;
358 }
359 }
360}