rgsl/types/
roots.rs

1//
2// A rust binding for the GSL library by Guillaume Gomez (guillaume1.gomez@gmail.com)
3//
4
5/*!
6# One dimensional Root-Finding
7
8This chapter describes routines for finding roots of arbitrary one-dimensional functions.
9The library provides low level components for a variety of iterative solvers and convergence
10tests. These can be combined by the user to achieve the desired solution, with full access to
11the intermediate steps of the iteration. Each class of methods uses the same framework, so
12that you can switch between solvers at runtime without needing to recompile your program.
13Each instance of a solver keeps track of its own state, allowing the solvers to be used in
14multi-threaded programs.
15
16## Overview
17
18One-dimensional root finding algorithms can be divided into two classes, root bracketing and
19root polishing. Algorithms which proceed by bracketing a root are guaranteed to converge.
20Bracketing algorithms begin with a bounded region known to contain a root. The size of
21this bounded region is reduced, iteratively, until it encloses the root to a desired tolerance.
22This provides a rigorous error estimate for the location of the root.
23
24The technique of root polishing attempts to improve an initial guess to the root. These
25algorithms converge only if started “close enough” to a root, and sacrifice a rigorous error
26bound for speed. By approximating the behavior of a function in the vicinity of a root they
27attempt to find a higher order improvement of an initial guess. When the behavior of the
28function is compatible with the algorithm and a good initial guess is available a polishing
29algorithm can provide rapid convergence.
30
31In GSL both types of algorithm are available in similar frameworks. The user provides
32a high-level driver for the algorithms, and the library provides the individual functions
33necessary for each of the steps. There are three main phases of the iteration. The steps are,
34• initialize solver state, s, for algorithm T
35• update s using the iteration T
36• test s for convergence, and repeat iteration if necessary
37
38The state for bracketing solvers is held in a gsl_root_fsolver struct. The updating
39procedure uses only function evaluations (not derivatives). The state for root polishing
40solvers is held in a gsl_root_fdfsolver struct. The updates require both the function and
41its derivative (hence the name fdf) to be supplied by the user.
42!*/
43
44use crate::Value;
45use ffi::FFI;
46use sys;
47use sys::libc::{c_double, c_void};
48
49ffi_wrapper!(
50    RootFSolverType,
51    *const sys::gsl_root_fsolver_type,
52    "The root bracketing algorithms described in this section require an initial interval which is
53guaranteed to contain a root—if a and b are the endpoints of the interval then f (a) must
54differ in sign from f (b). This ensures that the function crosses zero at least once in the
55interval. If a valid initial interval is used then these algorithm cannot fail, provided the
56function is well-behaved.
57Note that a bracketing algorithm cannot find roots of even degree, since these do not
58cross the x-axis."
59);
60
61impl RootFSolverType {
62    /// The bisection algorithm is the simplest method of bracketing the roots of a function.
63    /// It is the slowest algorithm provided by the library, with linear convergence.
64    /// On each iteration, the interval is bisected and the value of the function at the midpoint
65    /// is calculated. The sign of this value is used to determine which half of the interval does
66    /// not contain a root. That half is discarded to give a new, smaller interval containing
67    /// the root. This procedure can be continued indefinitely until the interval is sufficiently
68    /// small.
69    ///
70    /// At any time the current estimate of the root is taken as the midpoint of the interval.
71    #[doc(alias = "gsl_root_fsolver_bisection")]
72    pub fn bisection() -> RootFSolverType {
73        ffi_wrap!(gsl_root_fsolver_bisection)
74    }
75
76    /// The false position algorithm is a method of finding roots based on linear interpolation.
77    /// Its convergence is linear, but it is usually faster than bisection.
78    ///
79    /// On each iteration a line is drawn between the endpoints (a, f (a)) and (b, f (b)) and
80    /// the point where this line crosses the x-axis taken as a “midpoint”. The value of the
81    /// function at this point is calculated and its sign is used to determine which side of the
82    /// interval does not contain a root. That side is discarded to give a new, smaller interval
83    /// containing the root. This procedure can be continued indefinitely until the interval
84    /// is sufficiently small.
85    ///
86    /// The best estimate of the root is taken from the linear interpolation of the interval on
87    /// the current iteration.
88    #[doc(alias = "gsl_root_fsolver_brent")]
89    pub fn brent() -> RootFSolverType {
90        ffi_wrap!(gsl_root_fsolver_brent)
91    }
92
93    /// The Brent-Dekker method (referred to here as Brent’s method) combines an interpo-
94    /// lation strategy with the bisection algorithm. This produces a fast algorithm which is
95    /// still robust.
96
97    /// On each iteration Brent’s method approximates the function using an interpolating
98    /// curve. On the first iteration this is a linear interpolation of the two endpoints. For
99    /// subsequent iterations the algorithm uses an inverse quadratic fit to the last three
100    /// points, for higher accuracy. The intercept of the interpolating curve with the x-axis
101    /// is taken as a guess for the root. If it lies within the bounds of the current interval
102    /// then the interpolating point is accepted, and used to generate a smaller interval. If
103    /// the interpolating point is not accepted then the algorithm falls back to an ordinary
104    /// bisection step.
105    ///
106    /// The best estimate of the root is taken from the most recent interpolation or bisection.
107    #[doc(alias = "gsl_root_fsolver_falsepos")]
108    pub fn falsepos() -> RootFSolverType {
109        ffi_wrap!(gsl_root_fsolver_falsepos)
110    }
111}
112
113ffi_wrapper!(
114    RootFSolver<'a>,
115    *mut sys::gsl_root_fsolver,
116    gsl_root_fsolver_free
117    ;inner_call: sys::gsl_function_struct => sys::gsl_function_struct { function: None, params: std::ptr::null_mut() };
118    ;inner_closure: Option<Box<dyn Fn(f64) -> f64 + 'a>> => None;,
119    "This is a workspace for finding roots using methods which do not require derivatives."
120);
121
122impl<'a> RootFSolver<'a> {
123    /// This function returns a pointer to a newly allocated instance of a solver of type T.
124    ///
125    /// If there is insufficient memory to create the solver then the function returns a null
126    /// pointer and the error handler is invoked with an error code of `Value::NoMemory`.
127    #[doc(alias = "gsl_root_fsolver_alloc")]
128    pub fn new(t: RootFSolverType) -> Option<RootFSolver<'a>> {
129        let tmp = unsafe { sys::gsl_root_fsolver_alloc(t.unwrap_shared()) };
130
131        if tmp.is_null() {
132            None
133        } else {
134            Some(RootFSolver::wrap(tmp))
135        }
136    }
137
138    /// This function initializes, or reinitializes, an existing solver s to use the function f and
139    /// the initial search interval [x lower, x upper].
140    #[doc(alias = "gsl_root_fsolver_set")]
141    pub fn set<F: Fn(f64) -> f64 + 'a>(
142        &mut self,
143        f: F,
144        x_lower: f64,
145        x_upper: f64,
146    ) -> Result<(), Value> {
147        self.inner_call = wrap_callback!(f, F + 'a);
148        self.inner_closure = Some(Box::new(f));
149
150        let ret = unsafe {
151            sys::gsl_root_fsolver_set(self.unwrap_unique(), &mut self.inner_call, x_lower, x_upper)
152        };
153        result_handler!(ret, ())
154    }
155
156    /// The following function drives the iteration of each algorithm. Each function performs one
157    /// iteration to update the state of any solver of the corresponding type. The same func-
158    /// tion works for all solvers so that different methods can be substituted at runtime without
159    /// modifications to the code.
160    ///
161    /// This function performs a single iteration of the solver s. If the iteration encounters
162    /// an unexpected problem then an error code will be returned.
163    ///
164    /// The solver maintains a current best estimate of the root at all times. The bracketing
165    /// solvers also keep track of the current best interval bounding the root.
166    #[doc(alias = "gsl_root_fsolver_iterate")]
167    pub fn iterate(&mut self) -> Result<(), Value> {
168        let ret = unsafe { sys::gsl_root_fsolver_iterate(self.unwrap_unique()) };
169        result_handler!(ret, ())
170    }
171
172    /// Returns the solver type name.
173    #[doc(alias = "gsl_root_fsolver_name")]
174    pub fn name(&self) -> String {
175        unsafe {
176            let tmp = sys::gsl_root_fsolver_name(self.unwrap_shared());
177
178            String::from_utf8_lossy(::std::ffi::CStr::from_ptr(tmp).to_bytes()).to_string()
179        }
180    }
181
182    /// This function returns the current estimate of the root for the solver s.
183    #[doc(alias = "gsl_root_fsolver_root")]
184    pub fn root(&self) -> f64 {
185        unsafe { sys::gsl_root_fsolver_root(self.unwrap_shared()) }
186    }
187
188    /// These functions return the current bracketing interval for the solver s.
189    #[doc(alias = "gsl_root_fsolver_x_lower")]
190    pub fn x_lower(&self) -> f64 {
191        unsafe { sys::gsl_root_fsolver_x_lower(self.unwrap_shared()) }
192    }
193
194    /// These functions return the current bracketing interval for the solver s.
195    #[doc(alias = "gsl_root_fsolver_x_upper")]
196    pub fn x_upper(&self) -> f64 {
197        unsafe { sys::gsl_root_fsolver_x_upper(self.unwrap_shared()) }
198    }
199}
200
201ffi_wrapper!(
202    RootFdfSolverType,
203    *const sys::gsl_root_fdfsolver_type,
204    "The root polishing algorithms described in this section require an initial guess for the
205location of the root. There is no absolute guarantee of convergence—the function must be
206suitable for this technique and the initial guess must be sufficiently close to the root
207for it to work. When these conditions are satisfied then convergence is quadratic.
208These algorithms make use of both the function and its derivative."
209);
210
211impl RootFdfSolverType {
212    /// Newton’s Method is the standard root-polishing algorithm. The algorithm begins
213    /// with an initial guess for the location of the root. On each iteration, a line tangent to
214    /// the function f is drawn at that position. The point where this line crosses the x-axis
215    /// becomes the new guess.
216    #[doc(alias = "gsl_root_fdfsolver_newton")]
217    pub fn newton() -> RootFdfSolverType {
218        ffi_wrap!(gsl_root_fdfsolver_newton)
219    }
220
221    /// The secant method is a simplified version of Newton’s method which does not require
222    /// the computation of the derivative on every step.
223    #[doc(alias = "gsl_root_fdfsolver_secant")]
224    pub fn secant() -> RootFdfSolverType {
225        ffi_wrap!(gsl_root_fdfsolver_secant)
226    }
227
228    /// The Steffenson Method 1 provides the fastest convergence of all the routines. It com-
229    /// bines the basic Newton algorithm with an Aitken “delta-squared” acceleration.
230    #[doc(alias = "gsl_root_fdfsolver_steffenson")]
231    pub fn steffenson() -> RootFdfSolverType {
232        ffi_wrap!(gsl_root_fdfsolver_steffenson)
233    }
234}
235
236ffi_wrapper!(
237    RootFdfSolver<'a>,
238    *mut sys::gsl_root_fdfsolver,
239    gsl_root_fdfsolver_free
240    ;inner_call: sys::gsl_function_fdf_struct => sys::gsl_function_fdf_struct{f: None, df: None, fdf: None, params: std::ptr::null_mut()};
241    ;inner_f_closure: Option<Box<dyn Fn(f64) -> f64 + 'a>> => None;
242    ;inner_df_closure: Option<Box<dyn Fn(f64) -> f64 + 'a>> => None;
243    ;inner_fdf_closure: Option<Box<dyn Fn(f64, &mut f64, &mut f64) + 'a>> => None;,
244    "This is a workspace for finding roots using methods which do require derivatives."
245);
246
247impl<'a> RootFdfSolver<'a> {
248    /// This function returns a pointer to a newly allocated instance of a derivative-based
249    /// solver of type T.
250    ///
251    /// If there is insufficient memory to create the solver then the function returns a null
252    /// pointer and the error handler is invoked with an error code of `Value::NoMemory`.
253    #[doc(alias = "gsl_root_fdfsolver_alloc")]
254    pub fn new(t: RootFdfSolverType) -> Option<RootFdfSolver<'a>> {
255        let tmp = unsafe { sys::gsl_root_fdfsolver_alloc(t.unwrap_shared()) };
256
257        if tmp.is_null() {
258            None
259        } else {
260            Some(RootFdfSolver::wrap(tmp))
261        }
262    }
263
264    /// This function initializes, or reinitializes, an existing solver s to use the function and
265    /// derivative fdf and the initial guess root.
266    #[doc(alias = "gsl_root_fdfsolver_set")]
267    pub fn set<
268        F: Fn(f64) -> f64 + 'a,
269        DF: Fn(f64) -> f64 + 'a,
270        FDF: Fn(f64, &mut f64, &mut f64) + 'a,
271    >(
272        &mut self,
273        f: F,
274        df: DF,
275        fdf: FDF,
276        root: f64,
277    ) -> Result<(), Value> {
278        // convert rust functions to C
279        unsafe extern "C" fn inner_f<'a, F: Fn(f64) -> f64 + 'a>(
280            x: c_double,
281            params: *mut c_void,
282        ) -> f64 {
283            let f: &F = &*(params as *const F);
284            f(x)
285        }
286
287        unsafe extern "C" fn inner_df<'a, DF: Fn(f64) -> f64 + 'a>(
288            x: c_double,
289            params: *mut c_void,
290        ) -> f64 {
291            let df: &DF = &*(params as *const DF);
292            df(x)
293        }
294
295        unsafe extern "C" fn inner_fdf<'a, FDF: Fn(f64, &mut f64, &mut f64) + 'a>(
296            x: c_double,
297            params: *mut c_void,
298            y: *mut c_double,
299            dy: *mut c_double,
300        ) {
301            let fdf: &FDF = &*(params as *const FDF);
302            fdf(x, &mut *y, &mut *dy);
303        }
304
305        self.inner_call = sys::gsl_function_fdf {
306            f: Some(inner_f::<F>),
307            df: Some(inner_df::<DF>),
308            fdf: Some(inner_fdf::<FDF>),
309            params: &(&f, &df, &fdf) as *const _ as *mut _,
310        };
311        self.inner_f_closure = Some(Box::new(f));
312        self.inner_df_closure = Some(Box::new(df));
313        self.inner_fdf_closure = Some(Box::new(fdf));
314
315        let ret = unsafe {
316            sys::gsl_root_fdfsolver_set(self.unwrap_unique(), &mut self.inner_call, root)
317        };
318        result_handler!(ret, ())
319    }
320
321    /// The following function drives the iteration of each algorithm. Each function performs one
322    /// iteration to update the state of any solver of the corresponding type. The same func-
323    /// tion works for all solvers so that different methods can be substituted at runtime without
324    /// modifications to the code.
325    ///
326    /// This function performs a single iteration of the solver s. If the iteration encounters
327    /// an unexpected problem then an error code will be returned.
328    ///
329    /// The solver maintains a current best estimate of the root at all times. The bracketing
330    /// solvers also keep track of the current best interval bounding the root.
331    #[doc(alias = "gsl_root_fdfsolver_iterate")]
332    pub fn iterate(&mut self) -> Result<(), Value> {
333        let ret = unsafe { sys::gsl_root_fdfsolver_iterate(self.unwrap_unique()) };
334        result_handler!(ret, ())
335    }
336
337    /// Returns the solver type name.
338    #[doc(alias = "gsl_root_fdfsolver_name")]
339    pub fn name(&self) -> Option<&str> {
340        unsafe {
341            let ptr = sys::gsl_root_fdfsolver_name(self.unwrap_shared());
342
343            if ptr.is_null() {
344                return None;
345            }
346
347            let mut len = 0;
348            while *ptr.add(len) != 0 {
349                len += 1;
350            }
351
352            let slice = std::slice::from_raw_parts(ptr as *const _, len);
353            std::str::from_utf8(slice).ok()
354        }
355    }
356
357    /// This function returns the current estimate of the root for the solver s.
358    #[doc(alias = "gsl_root_fdfsolver_root")]
359    pub fn root(&self) -> f64 {
360        unsafe { sys::gsl_root_fdfsolver_root(self.unwrap_shared()) }
361    }
362}
363
364#[cfg(any(test, doctest))]
365mod test {
366    /// This doc block will be used to ensure that the closure can't be set everywhere!
367    ///
368    /// ```compile_fail
369    /// use rgsl::*;
370    ///
371    /// fn set(root: &mut RootFSolver) {
372    ///     let y = "lalal".to_owned();
373    ///     root.set(
374    ///         {|x| x * x - 5.;
375    ///         println!("==> {:?}", y);
376    ///         }, 0.0, 5.0);
377    /// }
378    ///
379    /// let mut root = RootFSolver::new(RootFSolverType::brent()).unwrap();
380    /// set(&mut root);
381    /// ```
382    ///
383    /// Same but a working version:
384    ///
385    /// ```
386    /// use rgsl::*;
387    ///
388    /// fn set(root: &mut RootFSolver) {
389    ///     root.set(|x| x * x - 5., 0.0, 5.0);
390    /// }
391    ///
392    /// let mut root = RootFSolver::new(RootFSolverType::brent()).unwrap();
393    /// set(&mut root);
394    /// let status = root.iterate();
395    /// ```
396    use super::*;
397    use roots::{test_delta, test_interval};
398
399    // support functions
400    fn quadratic_test_fn(x: f64) -> f64 {
401        x.powf(2.0) - 5.0
402    }
403
404    fn quadratic_test_fn_df(x: f64) -> f64 {
405        2.0 * x
406    }
407
408    fn quadratic_test_fn_fdf(x: f64, y: &mut f64, dy: &mut f64) {
409        *y = x.powf(2.0) - 5.0;
410        *dy = 2.0 * x;
411    }
412
413    #[test]
414    fn test_root() {
415        let mut root = RootFSolver::new(RootFSolverType::brent()).unwrap();
416        root.set(quadratic_test_fn, 0.0, 5.0).unwrap();
417
418        let max_iter = 10usize;
419        let epsabs = 0.0001;
420        let epsrel = 0.0000001;
421
422        let mut status = Value::Continue;
423        let mut iter = 0usize;
424
425        println!("Testing: {}", root.name());
426
427        println!("iter, \t [x_lo, x_hi], \t min, \t error");
428        while matches!(status, Value::Continue) && iter < max_iter {
429            root.iterate().unwrap();
430
431            // test for convergence
432            let r = root.root();
433            let x_lo = root.x_lower();
434            let x_hi = root.x_upper();
435
436            status = test_interval(x_lo, x_hi, epsabs, epsrel);
437
438            // check if iteration succeeded
439            if status == Value::Success {
440                println!("Converged");
441            }
442
443            println!(
444                "{} \t [{:.5}, {:.5}] \t {:.5} \t {:.5}",
445                iter,
446                x_lo,
447                x_hi,
448                r,
449                x_hi - x_lo
450            );
451            iter += 1;
452        }
453        assert!(matches!(status, Value::Success))
454    }
455
456    #[test]
457    fn test_root_fdf() {
458        //guess value
459        let r_expected = 5.0_f64.sqrt();
460        let guess_value = 1.0;
461
462        // setup solver
463        let mut root = RootFdfSolver::new(RootFdfSolverType::steffenson()).unwrap();
464        root.set(
465            quadratic_test_fn,
466            quadratic_test_fn_df,
467            quadratic_test_fn_fdf,
468            guess_value,
469        )
470        .unwrap();
471
472        // set up iterations
473        let max_iter = 20usize;
474        let epsabs = 0.0001;
475        let epsrel = 0.0000001;
476
477        let mut status = Value::Continue;
478        let mut iter = 0usize;
479
480        println!("Testing: {}", root.name().unwrap());
481
482        println!("iter, \t root, \t rel error \t abs error");
483
484        let mut x = guess_value;
485        while matches!(status, Value::Continue) && iter < max_iter {
486            root.iterate().unwrap();
487
488            // test for convergence
489            let x_0 = x;
490            x = root.root();
491            // check if iteration succeeded
492            status = test_delta(x, x_0, epsabs, epsrel);
493
494            if matches!(status, Value::Success) {
495                println!("Converged");
496            }
497
498            // print results
499            println!(
500                "{} \t {:.5} \t {:.5} \t {:.5}",
501                iter,
502                x,
503                x - x_0,
504                x - r_expected
505            );
506            iter += 1;
507        }
508        assert!(matches!(status, Value::Success))
509    }
510}