rgsl/types/
multifit_solver.rs

1//
2// A rust binding for the GSL library by Guillaume Gomez (guillaume1.gomez@gmail.com)
3//
4
5/*!
6# Nonlinear Least-Squares Fitting
7
8This chapter describes functions for multidimensional nonlinear least-squares fitting. The library
9provides low level components for a variety of iterative solvers and convergence tests. These can
10be combined by the user to achieve the desired solution, with full access to the intermediate steps
11of the iteration. Each class of methods uses the same framework, so that you can switch between
12solvers at runtime without needing to recompile your program. Each instance of a solver keeps track
13of its own state, allowing the solvers to be used in multi-threaded programs.
14
15## Overview
16
17The problem of multidimensional nonlinear least-squares fitting requires the minimization of the
18squared residuals of n functions, f_i, in p parameters, x_i,
19
20\Phi(x) = (1/2) || F(x) ||^2
21        = (1/2) \sum_{i=1}^{n} f_i(x_1, ..., x_p)^2
22All algorithms proceed from an initial guess using the linearization,
23
24\psi(p) = || F(x+p) || ~=~ || F(x) + J p ||
25where x is the initial point, p is the proposed step and J is the Jacobian matrix J_{ij} = d f_i /
26d x_j. Additional strategies are used to enlarge the region of convergence. These include requiring
27a decrease in the norm ||F|| on each step or using a trust region to avoid steps which fall outside
28the linear regime.
29
30To perform a weighted least-squares fit of a nonlinear model Y(x,t) to data (t_i, y_i) with
31independent Gaussian errors \sigma_i, use function components of the following form,
32
33f_i = (Y(x, t_i) - y_i) / \sigma_i
34Note that the model parameters are denoted by x in this chapter since the non-linear least-squares
35algorithms are described geometrically (i.e. finding the minimum of a surface). The independent
36variable of any data to be fitted is denoted by t.
37
38With the definition above the Jacobian is J_{ij} =(1 / \sigma_i) d Y_i / d x_j, where Y_i =
39Y(x,t_i).
40
41## High Level Driver
42
43These routines provide a high level wrapper that combine the iteration and convergence testing for
44easy use.
45*/
46
47/*
48C Equivalent code:
49
50```
51#include <stdlib.h>
52#include <stdio.h>
53#include <gsl/gsl_rng.h>
54#include <gsl/gsl_randist.h>
55#include <gsl/gsl_vector.h>
56#include <gsl/gsl_blas.h>
57#include <gsl/gsl_multifit_nlin.h>
58
59struct data {
60    size_t n;
61    double * y;
62    double * sigma;
63};
64
65void print_state(size_t iter, gsl_multifit_fdfsolver * s) {
66    printf("iter: %3u x = % 15.8f % 15.8f % 15.8f |f(x)| = %g\n",
67           iter,
68           gsl_vector_get (s->x, 0),
69           gsl_vector_get (s->x, 1),
70           gsl_vector_get (s->x, 2),
71           gsl_blas_dnrm2 (s->f));
72}
73
74int expb_f(const gsl_vector * x, void *params,
75           gsl_vector * f) {
76    size_t n = ((struct data *)params)->n;
77    double *y = ((struct data *)params)->y;
78    double *sigma = ((struct data *) params)->sigma;
79
80    double A = gsl_vector_get (x, 0);
81    double lambda = gsl_vector_get (x, 1);
82    double b = gsl_vector_get (x, 2);
83
84    size_t i;
85
86    for (i = 0; i < n; i++) {
87        /* Model Yi = A * exp(-lambda * i) + b */
88        double t = i;
89        double Yi = A * exp (-lambda * t) + b;
90        gsl_vector_set (f, i, (Yi - y[i])/sigma[i]);
91    }
92
93    return GSL_SUCCESS;
94}
95
96int expb_df(const gsl_vector * x, void *params,
97            gsl_matrix * J) {
98    size_t n = ((struct data *)params)->n;
99    double *sigma = ((struct data *) params)->sigma;
100
101    double A = gsl_vector_get (x, 0);
102    double lambda = gsl_vector_get (x, 1);
103
104    size_t i;
105
106    for (i = 0; i < n; i++) {
107        /* Jacobian matrix J(i,j) = dfi / dxj, */
108        /* where fi = (Yi - yi)/sigma[i],      */
109        /*       Yi = A * exp(-lambda * i) + b  */
110        /* and the xj are the parameters (A,lambda,b) */
111        double t = i;
112        double s = sigma[i];
113        double e = exp(-lambda * t);
114        gsl_matrix_set (J, i, 0, e/s);
115        gsl_matrix_set (J, i, 1, -t * A * e/s);
116        gsl_matrix_set (J, i, 2, 1/s);
117
118    }
119    return GSL_SUCCESS;
120}
121
122int expb_fdf(const gsl_vector * x, void *params,
123             gsl_vector * f, gsl_matrix * J) {
124    expb_f (x, params, f);
125    expb_df (x, params, J);
126
127    return GSL_SUCCESS;
128}
129
130int main(void) {
131    const gsl_multifit_fdfsolver_type *T;
132    gsl_multifit_fdfsolver *s;
133
134    int status;
135    size_t i, iter = 0;
136
137    const size_t n = 40;
138    const size_t p = 3;
139
140    gsl_matrix *covar = gsl_matrix_alloc (p, p);
141
142    double y[n], sigma[n];
143
144    struct data d = { n, y, sigma};
145
146    gsl_multifit_function_fdf f;
147
148    double x_init[3] = { 1.0, 0.0, 0.0 };
149
150    gsl_vector_view x = gsl_vector_view_array (x_init, p);
151
152    const gsl_rng_type * type;
153    gsl_rng * r;
154
155    gsl_rng_env_setup();
156
157    type = gsl_rng_default;
158    r = gsl_rng_alloc (type);
159
160    f.f = &expb_f;
161    f.df = &expb_df;
162    f.fdf = &expb_fdf;
163    f.n = n;
164    f.p = p;
165    f.params = &d;
166
167    /* This is the data to be fitted */
168
169    for (i = 0; i < n; i++) {
170        double t = i;
171        y[i] = 1.0 + 5 * exp (-0.1 * t) + gsl_ran_gaussian(r, 0.1);
172        sigma[i] = 0.1;
173        printf("data: %d %g %g\n", i, y[i], sigma[i]);
174    }
175
176    T = gsl_multifit_fdfsolver_lmsder;
177    s = gsl_multifit_fdfsolver_alloc (T, n, p);
178    gsl_multifit_fdfsolver_set (s, &f, &x.vector);
179
180    print_state (iter, s);
181
182    do {
183        iter++;
184        status = gsl_multifit_fdfsolver_iterate (s);
185
186        printf ("status = %s\n", gsl_strerror (status));
187
188        print_state (iter, s);
189
190        if (status)
191          break;
192
193        status = gsl_multifit_test_delta (s->dx, s->x,
194                          1e-4, 1e-4);
195    } while (status == GSL_CONTINUE && iter < 500);
196
197    gsl_multifit_covar (s->J, 0.0, covar);
198
199    gsl_matrix_fprintf (stdout, covar, "%g");
200
201#define FIT(i) gsl_vector_get(s->x, i)
202#define ERR(i) sqrt(gsl_matrix_get(covar,i,i))
203
204    printf("A      = %.5f +/- %.5f\n", FIT(0), ERR(0));
205    printf("lambda = %.5f +/- %.5f\n", FIT(1), ERR(1));
206    printf("b      = %.5f +/- %.5f\n", FIT(2), ERR(2));
207
208    printf ("status = %s\n", gsl_strerror (status));
209
210    gsl_multifit_fdfsolver_free (s);
211    return 0;
212}
213```
214*/
215
216use crate::{Value, VectorF64, View};
217use ffi::{self, FFI};
218use std::os::raw::{c_int, c_void};
219
220ffi_wrapper!(MultiFitFSolverType, *mut sys::gsl_multifit_fsolver_type);
221
222// pub struct MultiFitFunction<F: Fn(x: &::VectorF64, f: &mut crate::VectorF64)> {
223//     pub f: Box<F>,
224//     /// Number of functions.
225//     pub n: usize,
226//     /// Number of independent variables.
227//     pub p,
228// }
229
230pub struct MultiFitFunction(pub sys::gsl_multifit_function);
231
232ffi_wrapper!(
233    MultiFitFSolver,
234    *mut sys::gsl_multifit_fsolver,
235    gsl_multifit_fsolver_free
236);
237
238impl MultiFitFSolver {
239    /// This function returns a pointer to a newly allocated instance of a solver of type T for n
240    /// observations and p parameters. The number of observations n must be greater than or equal to
241    /// parameters p.
242    ///
243    /// If there is insufficient memory to create the solver then the function returns a null
244    /// pointer and the error handler is invoked with an error code of `Value::NoMemory`.
245    #[doc(alias = "gsl_multifit_fsolver_alloc")]
246    pub fn new(t: &MultiFitFSolverType, n: usize, p: usize) -> Option<MultiFitFSolver> {
247        let tmp = unsafe { sys::gsl_multifit_fsolver_alloc(t.unwrap_shared(), n, p) };
248
249        if tmp.is_null() {
250            None
251        } else {
252            Some(MultiFitFSolver::wrap(tmp))
253        }
254    }
255
256    #[doc(alias = "gsl_multifit_fsolver_set")]
257    #[allow(unknown_lints, clippy::needless_pass_by_ref_mut)]
258    pub fn set(&mut self, f: &mut MultiFitFunction, x: &mut VectorF64) -> Result<(), Value> {
259        // unsafe {
260        //     let func = (*self.0).function;
261        //     if !func.is_null() {
262        //         Box::from_raw((*func).params);
263        //     }
264        // }
265        let ret = unsafe {
266            sys::gsl_multifit_fsolver_set(self.unwrap_unique(), &mut f.0, x.unwrap_shared())
267        };
268        result_handler!(ret, ())
269    }
270
271    #[doc(alias = "gsl_multifit_fsolver_iterate")]
272    pub fn iterate(&mut self) -> Result<(), Value> {
273        let ret = unsafe { sys::gsl_multifit_fsolver_iterate(self.unwrap_unique()) };
274        result_handler!(ret, ())
275    }
276
277    #[doc(alias = "gsl_multifit_fsolver_name")]
278    pub fn name(&self) -> String {
279        unsafe {
280            let tmp = sys::gsl_multifit_fsolver_name(self.unwrap_shared());
281
282            String::from_utf8_lossy(::std::ffi::CStr::from_ptr(tmp).to_bytes()).to_string()
283        }
284    }
285
286    #[doc(alias = "gsl_multifit_fsolver_position")]
287    pub fn position(&self) -> View<'_, VectorF64> {
288        unsafe { View::new(sys::gsl_multifit_fsolver_position(self.unwrap_shared())) }
289    }
290}
291
292ffi_wrapper!(
293    MultiFitFdfSolver,
294    *mut sys::gsl_multifit_fdfsolver,
295    gsl_multifit_fdfsolver_free
296);
297
298impl MultiFitFdfSolver {
299    /// This function returns a pointer to a newly allocated instance of a solver of type T for n
300    /// observations and p parameters. The number of observations n must be greater than or equal
301    /// to parameters p.
302    #[doc(alias = "gsl_multifit_fdfsolver_alloc")]
303    pub fn new(_type: &MultiFitFdfSolverType, n: usize, p: usize) -> Option<MultiFitFdfSolver> {
304        let s = unsafe { sys::gsl_multifit_fdfsolver_alloc(_type.unwrap_shared(), n, p) };
305        if s.is_null() {
306            None
307        } else {
308            Some(MultiFitFdfSolver::wrap(s))
309        }
310    }
311
312    /// This function initializes, or reinitializes, an existing solver s to use the function f and
313    /// the initial guess x.
314    #[doc(alias = "gsl_multifit_fdfsolver_set")]
315    pub fn set(&mut self, f: &mut MultiFitFunctionFdf, x: &VectorF64) -> Result<(), Value> {
316        let ret = unsafe {
317            sys::gsl_multifit_fdfsolver_set(self.unwrap_unique(), f.to_raw(), x.unwrap_shared())
318        };
319        result_handler!(ret, ())
320    }
321
322    pub fn x(&self) -> View<'_, VectorF64> {
323        unsafe { View::new((*self.unwrap_shared()).x) }
324    }
325
326    pub fn f(&self) -> View<'_, VectorF64> {
327        unsafe { View::new((*self.unwrap_shared()).f) }
328    }
329
330    pub fn dx(&self) -> View<'_, VectorF64> {
331        unsafe { View::new((*self.unwrap_shared()).dx) }
332    }
333
334    pub fn g(&self) -> View<'_, VectorF64> {
335        unsafe { View::new((*self.unwrap_shared()).g) }
336    }
337
338    pub fn sqrt_wts(&self) -> View<'_, VectorF64> {
339        unsafe { View::new((*self.unwrap_shared()).sqrt_wts) }
340    }
341
342    #[doc(alias = "gsl_multifit_fdfsolver_name")]
343    pub fn name(&self) -> String {
344        unsafe {
345            let tmp = sys::gsl_multifit_fdfsolver_name(self.unwrap_shared());
346
347            String::from_utf8_lossy(::std::ffi::CStr::from_ptr(tmp).to_bytes()).to_string()
348        }
349    }
350
351    /// This function performs a single iteration of the solver s. If the iteration encounters an
352    /// unexpected problem then an error code will be returned. The solver maintains a current
353    /// estimate of the best-fit parameters at all times.
354    #[doc(alias = "gsl_multifit_fdfsolver_iterate")]
355    pub fn iterate(&mut self) -> Result<(), Value> {
356        let ret = unsafe { sys::gsl_multifit_fdfsolver_iterate(self.unwrap_unique()) };
357        result_handler!(ret, ())
358    }
359
360    /// This function returns the current position (i.e. best-fit parameters) s->x of the solver s.
361    #[doc(alias = "gsl_multifit_fdfsolver_position")]
362    pub fn position(&self) -> View<'_, VectorF64> {
363        unsafe { View::new(sys::gsl_multifit_fdfsolver_position(self.unwrap_shared())) }
364    }
365
366    /// These functions iterate the solver s for a maximum of maxiter iterations. After each
367    /// iteration, the system is tested for convergence using gsl_multifit_test_delta with the
368    /// error tolerances epsabs and epsrel.
369    // checker:ignore
370    #[allow(unused_assignments)]
371    #[doc(alias = "gsl_multifit_test_delta")]
372    pub fn driver(&mut self, max_iter: usize, epsabs: f64, epsrel: f64) -> Result<(), Value> {
373        let mut status = Value::Failure;
374        let ptr = self.unwrap_shared();
375
376        if !ptr.is_null() {
377            let mut iter = 0usize;
378            loop {
379                self.iterate()?;
380
381                /* test for convergence */
382                status = Value::from(unsafe {
383                    sys::gsl_multifit_test_delta((*ptr).dx, (*ptr).x, epsabs, epsrel)
384                });
385                iter += 1;
386                if status != Value::Continue || iter >= max_iter {
387                    break;
388                }
389            }
390        }
391
392        if status != Value::Success {
393            Err(status)
394        } else {
395            Ok(())
396        }
397    }
398}
399
400ffi_wrapper!(
401    MultiFitFdfSolverType,
402    *const sys::gsl_multifit_fdfsolver_type
403);
404
405impl MultiFitFdfSolverType {
406    #[doc(alias = "gsl_multifit_fdfsolver_lmder")]
407    pub fn lmder() -> MultiFitFdfSolverType {
408        ffi_wrap!(gsl_multifit_fdfsolver_lmder)
409    }
410
411    #[doc(alias = "gsl_multifit_fdfsolver_lmsder")]
412    pub fn lmsder() -> MultiFitFdfSolverType {
413        ffi_wrap!(gsl_multifit_fdfsolver_lmsder)
414    }
415}
416
417pub struct MultiFitFunctionFdf {
418    pub f: Option<Box<dyn Fn(::VectorF64, crate::VectorF64) -> Value>>,
419    pub df: Option<Box<dyn Fn(::VectorF64, crate::MatrixF64) -> Value>>,
420    pub fdf: Option<Box<dyn Fn(::VectorF64, crate::VectorF64, crate::MatrixF64) -> Value>>,
421    pub n: usize,
422    pub p: usize,
423    intern: sys::gsl_multifit_function_fdf,
424}
425
426impl MultiFitFunctionFdf {
427    #[doc(alias = "gsl_multifit_function_fdf")]
428    pub fn new(n: usize, p: usize, nevalf: usize, nevaldf: usize) -> MultiFitFunctionFdf {
429        MultiFitFunctionFdf {
430            f: None,
431            df: None,
432            fdf: None,
433            n,
434            p,
435            intern: sys::gsl_multifit_function_fdf {
436                f: Some(f),
437                df: Some(df),
438                fdf: Some(fdf),
439                n,
440                p,
441                params: std::ptr::null_mut(),
442                nevalf,
443                nevaldf,
444            },
445        }
446    }
447
448    #[allow(clippy::wrong_self_convention)]
449    fn to_raw(&mut self) -> *mut sys::gsl_multifit_function_fdf {
450        self.intern.n = self.n;
451        self.intern.p = self.p;
452        self.intern.params = self as *mut MultiFitFunctionFdf as *mut c_void;
453        &mut self.intern
454    }
455}
456
457unsafe extern "C" fn f(
458    x: *const sys::gsl_vector,
459    params: *mut c_void,
460    pf: *mut sys::gsl_vector,
461) -> c_int {
462    let t = params as *mut MultiFitFunctionFdf;
463    if let Some(ref i_f) = (*t).f {
464        i_f(
465            ffi::FFI::soft_wrap(x as usize as *mut _),
466            ffi::FFI::soft_wrap(pf),
467        )
468        .into()
469    } else {
470        Value::Success.into()
471    }
472}
473
474unsafe extern "C" fn df(
475    x: *const sys::gsl_vector,
476    params: *mut c_void,
477    pdf: *mut sys::gsl_matrix,
478) -> c_int {
479    let t = params as *mut MultiFitFunctionFdf;
480    if let Some(ref i_df) = (*t).df {
481        i_df(
482            ffi::FFI::soft_wrap(x as usize as *mut _),
483            ffi::FFI::soft_wrap(pdf),
484        )
485        .into()
486    } else {
487        Value::Success.into()
488    }
489}
490
491unsafe extern "C" fn fdf(
492    x: *const sys::gsl_vector,
493    params: *mut c_void,
494    pf: *mut sys::gsl_vector,
495    pdf: *mut sys::gsl_matrix,
496) -> c_int {
497    let t = params as *mut MultiFitFunctionFdf;
498    if let Some(ref i_fdf) = (*t).fdf {
499        i_fdf(
500            ffi::FFI::soft_wrap(x as usize as *mut _),
501            ffi::FFI::soft_wrap(pf),
502            ffi::FFI::soft_wrap(pdf),
503        )
504        .into()
505    } else {
506        Value::Success.into()
507    }
508}