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}