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>
impl<'cost, 'param> CurveFitProblem1DBuilder<'cost, 'param>
pub fn new() -> Self
Sourcepub fn func(self, func: impl Into<CurveFunctionType>) -> Self
pub fn func(self, func: impl Into<CurveFunctionType>) -> Self
Add model function.
Sourcepub fn inverse_error(self, inv_err: &'cost [f64]) -> Self
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.
Sourcepub fn parameters(self, parameters: &'param [f64]) -> Self
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.
Sourcepub fn lower_bounds(self, lower_bounds: &'param [Option<f64>]) -> Self
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.
Sourcepub fn upper_bounds(self, upper_bounds: &'param [Option<f64>]) -> Self
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.
Sourcepub fn constant(self, indexes: &'param [usize]) -> Self
pub fn constant(self, indexes: &'param [usize]) -> Self
Make parameters constant, i.e. they will not be fitted.
Sourcepub fn loss(self, loss: LossFunction) -> Self
pub fn loss(self, loss: LossFunction) -> Self
Add optional loss function, if not given the trivial loss is assumed.
Sourcepub fn build(
self,
) -> Result<CurveFitProblem1D<'cost>, CurveFitProblemBuildError>
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.