Struct CurveFitProblem1DBuilder

Source
pub struct CurveFitProblem1DBuilder<'cost, 'param> {
    pub func: Option<CurveFunctionType>,
    pub x: Option<&'cost [f64]>,
    pub y: Option<&'cost [f64]>,
    pub inverse_error: Option<&'cost [f64]>,
    pub parameters: Option<&'param [f64]>,
    pub lower_bounds: Option<&'param [Option<f64>]>,
    pub upper_bounds: Option<&'param [Option<f64>]>,
    pub constant_parameters: Option<&'param [usize]>,
    pub loss: Option<LossFunction>,
}
Expand description

Builder for CurveFitProblem1D.

§Examples

§Loss function and data point errors

Fit linear function y = a * x + b to the data points:

use ceres_solver::curve_fit::{CurveFitProblem1D, CurveFunctionType};
use ceres_solver::loss::LossFunction;
use ceres_solver::solver::SolverOptions;

// Linear model
fn model(
    x: f64,
    parameters: &[f64],
    y: &mut f64,
    jacobians: Option<&mut [Option<f64>]>,
) -> bool {
    let &[a, b]: &[f64; 2] = parameters.try_into().unwrap();
    *y = a * x + b;
    if let Some(jacobians) = jacobians {
        let [d_da, d_db]: &mut [Option<f64>; 2] = jacobians.try_into().unwrap();
        if let Some(d_da) = d_da {
            *d_da = x;
        }
        if let Some(d_db) = d_db {
            *d_db = 1.0;
        }
    }
    true
}

let a = 3.0;
let b = -2.0;
let x: Vec<_> = (0..100).map(|i| i as f64).collect();
let y: Vec<_> = x.iter().map(|&x| a * x + b).collect();
// optional data points inverse errors, assumed to be positive
let inverse_error: Vec<_> = x.iter().map(|&x| (x + 1.0) / 100.0).collect();

let func: CurveFunctionType = Box::new(model);
let problem = CurveFitProblem1D::builder()
    // Model function
    .func(func)
    // Initial parameter guess
    .parameters(&[1.0, 0.0])
    // Data points, inverse errors are optional, if no given unity errors assumed.
    .x(&x)
    .y(&y)
    .inverse_error(&inverse_error)
    // Loss function is optional, if not given trivial loss is assumed.
    .loss(LossFunction::cauchy(1.0))
    .build()
    .unwrap();
let solution = problem.solve(&SolverOptions::default());

println!("{}", solution.summary.full_report());

assert!(f64::abs(a - solution.parameters[0]) < 1e-8);
assert!(f64::abs(b - solution.parameters[1]) < 1e-8);

§Constant parameters

Sometimes it is useful to fix some parameters and optimize only the rest. Let’s consider the curve y = a * x^k + b and compare two cases: when k is unity and when it is optimized.

use ceres_solver::curve_fit::{CurveFitProblem1D, CurveFunctionType};
use ceres_solver::SolverOptions;
use ceres_solver_sys::cxx::S;

fn model(
    x: f64,
    parameters: &[f64],
    y: &mut f64,
    jacobians: Option<&mut [Option<f64>]>,
) -> bool {
    let &[k, a, b]: &[f64; 3] = parameters.try_into().unwrap();
    *y = a * x.powf(k) + b;
    if let Some(jacobians) = jacobians {
        let [d_dk, d_da, d_db]: &mut [Option<f64>; 3] = jacobians.try_into().unwrap();
        if let Some(d_dk) = d_dk {
            *d_dk = a * x.powf(k) * x.ln();
        }
        if let Some(d_da) = d_da {
            *d_da = x.powf(k);
        }
        if let Some(d_db) = d_db {
            *d_db = 1.0;
        }
    }
    true
}

let true_a = 3.0;
let true_b = -2.0;
let true_k = 2.0;
let fixed_k = 1.0;
assert_ne!(true_a, fixed_k);

// Generate data
let x: Vec<_> = (1..101).map(|i| i as f64 / 100.0).collect();
let y: Vec<_> = x
    .iter()
    .map(|&x| {
        let mut y = 0.0;
        model(x, &[true_k, true_a, true_b], &mut y, None);
        y
    })
    .collect();

let func: CurveFunctionType = Box::new(model);
let solution_variable_k = CurveFitProblem1D::builder()
    .func(func)
    .parameters(&[1.0, 1.0, 1.0])
    .x(&x)
    .y(&y)
    .build()
    .unwrap()
    .solve(&SolverOptions::default());
assert!((true_k - solution_variable_k.parameters[0]).abs() < 1e-8);
assert!((true_a - solution_variable_k.parameters[1]).abs() < 1e-8);
assert!((true_b - solution_variable_k.parameters[2]).abs() < 1e-8);

let func: CurveFunctionType = Box::new(model);
let solution_fixed_k_1 = CurveFitProblem1D::builder()
    .func(func)
    .parameters(&[fixed_k, 1.0, 1.0])
    .constant(&[0]) // indexes of the fixed parameters
    .x(&x)
    .y(&y)
    .build()
    .unwrap()
    .solve(&SolverOptions::default());
assert!((fixed_k - solution_fixed_k_1.parameters[0]).abs() < 1e-8);

assert!(solution_variable_k.summary.final_cost() < solution_fixed_k_1.summary.final_cost());

Fields§

§func: Option<CurveFunctionType>

Model function

§x: Option<&'cost [f64]>

Independent coordinates for data

§y: Option<&'cost [f64]>

Values for data

§inverse_error: Option<&'cost [f64]>

Optional inverse errors - square root of the weight

§parameters: Option<&'param [f64]>

Initial parameters’ guess

§lower_bounds: Option<&'param [Option<f64>]>

Optional lower bounds for parameters

§upper_bounds: Option<&'param [Option<f64>]>

Optional upper bounds for parameters

§constant_parameters: Option<&'param [usize]>

Constant parameters, they will not be optimized.

§loss: Option<LossFunction>

Optional loss function

Implementations§

Source§

impl<'cost, 'param> CurveFitProblem1DBuilder<'cost, 'param>

Source

pub fn new() -> Self

Source

pub fn func(self, func: impl Into<CurveFunctionType>) -> Self

Add model function.

Source

pub fn x(self, x: &'cost [f64]) -> Self

Add independent parameter values for the data points.

Source

pub fn y(self, y: &'cost [f64]) -> Self

Add values for the data points.

Source

pub fn inverse_error(self, inv_err: &'cost [f64]) -> Self

Add optional inverse errors for the data points. They must to be positive: think about them as the inverse y’s uncertainties, or square root of the data point weight. The residual would be (y - model(x)) * inverse_error. If not given, unity valueas are assumed.

Source

pub fn parameters(self, parameters: &'param [f64]) -> Self

Add initial parameter guess slice, it is borrowed until CurveFitProblem1DBuilder::build() call only, there it will be copied to the CurveFitProblem1D instance.

Source

pub fn lower_bounds(self, lower_bounds: &'param [Option<f64>]) -> Self

Add optional lower bounds for parameters, in the same order as parameters themselves. If not given, no lower bounds are assumed. If some parameter has no lower bound, use None.

Source

pub fn upper_bounds(self, upper_bounds: &'param [Option<f64>]) -> Self

Add optional upper bounds for parameters, in the same order as parameters themselves. If not given, no upper bounds are assumed. If some parameter has no upper bound, use None.

Source

pub fn constant(self, indexes: &'param [usize]) -> Self

Make parameters constant, i.e. they will not be fitted.

Source

pub fn loss(self, loss: LossFunction) -> Self

Add optional loss function, if not given the trivial loss is assumed.

Source

pub fn build( self, ) -> Result<CurveFitProblem1D<'cost>, CurveFitProblemBuildError>

Build the CurveFitProblem1D instance. Returns Err if one of the mandatory fields is missed or data slices have inconsistent lengths.

Trait Implementations§

Source§

impl<'cost, 'param> Default for CurveFitProblem1DBuilder<'cost, 'param>

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

§

impl<'cost, 'param> Freeze for CurveFitProblem1DBuilder<'cost, 'param>

§

impl<'cost, 'param> !RefUnwindSafe for CurveFitProblem1DBuilder<'cost, 'param>

§

impl<'cost, 'param> !Send for CurveFitProblem1DBuilder<'cost, 'param>

§

impl<'cost, 'param> !Sync for CurveFitProblem1DBuilder<'cost, 'param>

§

impl<'cost, 'param> Unpin for CurveFitProblem1DBuilder<'cost, 'param>

§

impl<'cost, 'param> !UnwindSafe for CurveFitProblem1DBuilder<'cost, 'param>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.