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}