use crate::core::math::{MatTransposeVec, Scalar, VectorIndex, VectorLen};
use crate::core::problem::{CostFunction, Gradient, Jacobian, Residual};
mod constraints;
pub(crate) mod evaluation;
#[cfg(test)]
mod iterative_trf_api_prototype;
mod losses;
#[cfg(test)]
mod tests;
pub use losses::{ArctanLoss, CauchyLoss, HuberLoss, SoftL1Loss, SquaredLoss};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct LossEvaluation<F: Scalar = f64> {
pub value: F,
pub first_derivative: F,
pub second_derivative: F,
}
pub trait LossFunction<F: Scalar = f64> {
fn evaluate(&self, z: F) -> LossEvaluation<F>;
fn evaluate_scaled(&self, residual: F, scale: F) -> LossEvaluation<F> {
let t = residual / scale;
let z = t * t;
let invalid = || LossEvaluation {
value: F::infinity(),
first_derivative: F::nan(),
second_derivative: F::nan(),
};
if !z.is_finite() || (z == F::zero() && residual != F::zero()) {
return invalid();
}
let value = self.evaluate(z);
if !value.value.is_finite()
|| value.value < F::zero()
|| !value.first_derivative.is_finite()
|| value.first_derivative < F::zero()
|| !value.second_derivative.is_finite()
{
return invalid();
}
let root = scale * value.value.sqrt();
LossEvaluation {
value: (F::from_f64(0.5).unwrap() * root) * root,
first_derivative: value.first_derivative * residual,
second_derivative: value.first_derivative
+ F::from_f64(2.0).unwrap() * (value.second_derivative * z),
}
}
}
impl<F: Scalar, L: LossFunction<F> + ?Sized> LossFunction<F> for &L {
fn evaluate(&self, z: F) -> LossEvaluation<F> {
(**self).evaluate(z)
}
fn evaluate_scaled(&self, residual: F, scale: F) -> LossEvaluation<F> {
(**self).evaluate_scaled(residual, scale)
}
}
#[derive(Clone, Copy, Debug)]
pub struct RobustLeastSquares<P, L, F: Scalar = f64> {
problem: P,
loss: L,
scale: F,
}
impl<P, L, F: Scalar> RobustLeastSquares<P, L, F> {
pub fn new(problem: P, loss: L) -> Self {
Self {
problem,
loss,
scale: F::one(),
}
}
pub fn with_scale(mut self, scale: F) -> Self {
assert!(
scale.is_finite() && scale > F::zero(),
"residual scale must be finite and positive"
);
self.scale = scale;
self
}
pub fn inner(&self) -> &P {
&self.problem
}
pub fn loss(&self) -> &L {
&self.loss
}
pub fn scale(&self) -> F {
self.scale
}
pub fn into_inner(self) -> P {
self.problem
}
}
struct Term<F> {
cost: F,
derivative: F,
curvature: F,
}
impl<P, L: LossFunction<F>, F: Scalar> RobustLeastSquares<P, L, F> {
fn term(&self, r: F) -> Option<Term<F>> {
if !r.is_finite() {
return None;
}
let value = self.loss.evaluate_scaled(r, self.scale);
if !value.value.is_finite()
|| value.value < F::zero()
|| !value.first_derivative.is_finite()
|| (r > F::zero() && value.first_derivative < F::zero())
|| (r < F::zero() && value.first_derivative > F::zero())
|| !value.second_derivative.is_finite()
{
return None;
}
Some(Term {
cost: value.value,
derivative: value.first_derivative,
curvature: value.second_derivative,
})
}
pub(crate) fn residual_cost<V: VectorLen + VectorIndex<F>>(
&self,
r: &V,
) -> F {
let mut sum = F::zero();
for i in 0..r.vec_len() {
let Some(term) = self.term(r.get_scalar(i)) else {
return F::infinity();
};
sum = sum + term.cost;
}
sum
}
fn model_residual<V: Clone + VectorLen + VectorIndex<F>>(
&self,
r: &V,
) -> Option<(V, Vec<F>)> {
let mut model = r.clone();
let mut factors = Vec::with_capacity(r.vec_len());
let mut cost = F::zero();
for i in 0..r.vec_len() {
let term = self.term(r.get_scalar(i))?;
cost = cost + term.cost;
let factor = term.curvature.max(F::epsilon()).sqrt();
let value = term.derivative / factor;
if !value.is_finite() {
return None;
}
model.set_scalar(i, value);
factors.push(factor);
}
cost.is_finite().then_some((model, factors))
}
fn gradient_from<V: VectorLen + VectorIndex<F>, M: MatTransposeVec<V>>(
&self,
mut r: V,
j: M,
) -> V {
let mut valid = true;
for i in 0..r.vec_len() {
let derivative = self.term(r.get_scalar(i)).map_or_else(
|| {
valid = false;
F::nan()
},
|t| t.derivative,
);
r.set_scalar(i, derivative);
}
let mut gradient = j.mat_transpose_vec(&r);
if !valid {
for i in 0..gradient.vec_len() {
gradient.set_scalar(i, F::nan());
}
}
gradient
}
}
impl<P, L, F> CostFunction for RobustLeastSquares<P, L, F>
where
F: Scalar,
P: Residual,
P::Output: VectorLen + VectorIndex<F>,
L: LossFunction<F>,
{
type Param = P::Param;
type Output = F;
type Error = P::Error;
fn cost(&self, x: &P::Param) -> Result<F, P::Error> {
Ok(self.residual_cost(&self.problem.residual(x)?))
}
}
impl<P, L, V, F> Gradient for RobustLeastSquares<P, L, F>
where
F: Scalar,
P: Residual<Param = V, Output = V> + Jacobian,
P::Jacobian: MatTransposeVec<V>,
V: VectorLen + VectorIndex<F>,
L: LossFunction<F>,
{
type Gradient = V;
fn gradient(&self, x: &V) -> Result<V, P::Error> {
let (r, j) = self.problem.residual_and_jacobian(x)?;
Ok(self.gradient_from(r, j))
}
fn cost_and_gradient(&self, x: &V) -> Result<(F, V), P::Error> {
let (r, j) = self.problem.residual_and_jacobian(x)?;
Ok((self.residual_cost(&r), self.gradient_from(r, j)))
}
}
impl<P, L, F> crate::ProblemConstraints for RobustLeastSquares<P, L, F>
where
P: crate::ProblemConstraints,
F: crate::Scalar,
{
type Constraints = P::Constraints;
fn constraint_set(
&self,
) -> <Self::Constraints as crate::ConstraintDescription>::Borrowed<'_> {
self.problem.constraint_set()
}
}