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}