use crate::{
traits::{CostFunction, Gradient, LogDensity},
LinearAlgebra, RealScalar, Vector,
};
use std::convert::Infallible;
pub struct Rosenbrock {
pub n: usize,
}
impl<T, B> CostFunction<T, B> for Rosenbrock
where
T: RealScalar,
B: LinearAlgebra<T>,
{
fn evaluate(&self, x: &Vector<T, B>, _args: &()) -> Result<T, Infallible> {
let hundred = T::literal(100.0);
Ok((0..(self.n - 1))
.map(|i| {
hundred * (x.get(i + 1) - x.get(i).powi(2)).powi(2) + (T::one() - x.get(i)).powi(2)
})
.fold(T::zero(), |sum, value| sum + value))
}
}
impl<T, B> Gradient<T, B> for Rosenbrock
where
T: RealScalar,
B: LinearAlgebra<T>,
{
#[allow(clippy::option_if_let_else)]
fn gradient(&self, x: &Vector<T, B>, _args: &()) -> Result<Vector<T, B>, Infallible> {
let two = T::literal(2.0);
let two_hundred = T::literal(200.0);
let four_hundred = T::literal(400.0);
let gradient = if let Some(values) = x.as_slice() {
(0..self.n)
.map(|index| {
if index == 0 {
-four_hundred * values[0] * (values[1] - values[0].powi(2))
- two * (T::one() - values[0])
} else if index == self.n - 1 {
two_hundred * (values[index] - values[index - 1].powi(2))
} else {
two_hundred * (values[index] - values[index - 1].powi(2))
- four_hundred
* values[index]
* (values[index + 1] - values[index].powi(2))
- two * (T::one() - values[index])
}
})
.collect()
} else {
(0..self.n)
.map(|index| {
if index == 0 {
-four_hundred * x.get(0) * (x.get(1) - x.get(0).powi(2))
- two * (T::one() - x.get(0))
} else if index == self.n - 1 {
two_hundred * (x.get(index) - x.get(index - 1).powi(2))
} else {
two_hundred * (x.get(index) - x.get(index - 1).powi(2))
- four_hundred
* x.get(index)
* (x.get(index + 1) - x.get(index).powi(2))
- two * (T::one() - x.get(index))
}
})
.collect()
};
Ok(Vector::<T, B>::from_vec(gradient))
}
}
impl<T, B> LogDensity<T, B> for Rosenbrock
where
T: RealScalar,
B: LinearAlgebra<T>,
{
fn log_density(&self, x: &Vector<T, B>, args: &()) -> Result<T, Infallible> {
Ok(-<Self as CostFunction<T, B>>::evaluate(self, x, args)?.ln())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rosenbrock_evaluate_at_minimum() {
let f = Rosenbrock { n: 2 };
let x = Vector::<f64>::from_vec(vec![1.0, 1.0]);
let val = CostFunction::evaluate(&f, &x, &()).unwrap();
assert_eq!(val, 0.0); }
#[test]
fn test_rosenbrock_evaluate_known_point() {
let f = Rosenbrock { n: 2 };
let x = Vector::<f64>::from_vec(vec![0.0, 0.0]);
let val = CostFunction::evaluate(&f, &x, &()).unwrap();
assert_eq!(val, 1.0);
}
#[test]
fn test_rosenbrock_evaluate_three_dimensions() {
let f = Rosenbrock { n: 3 };
let x = Vector::<f64>::from_vec(vec![1.0, 1.0, 1.0]);
let val = CostFunction::evaluate(&f, &x, &()).unwrap();
assert_eq!(val, 0.0);
}
#[test]
fn generic_problem_supports_f32_and_f64_together() {
let f = Rosenbrock { n: 2 };
let x32 = Vector::<f32>::from_vec(vec![1.0, 1.0]);
let x64 = Vector::<f64>::from_vec(vec![1.0, 1.0]);
assert_eq!(
<Rosenbrock as CostFunction<f32>>::evaluate(&f, &x32, &()).unwrap(),
0.0
);
assert_eq!(
<Rosenbrock as CostFunction<f64>>::evaluate(&f, &x64, &()).unwrap(),
0.0
);
}
#[test]
fn test_rosenbrock_log_density_at_minimum() {
let f = Rosenbrock { n: 2 };
let x = Vector::<f64>::from_vec(vec![1.0, 1.0]);
let val = LogDensity::log_density(&f, &x, &()).unwrap();
assert!(val.is_infinite() && val.is_sign_positive());
}
#[test]
fn test_rosenbrock_log_density_known_point() {
let f = Rosenbrock { n: 2 };
let x = Vector::<f64>::from_vec(vec![0.0, 0.0]);
let val = LogDensity::log_density(&f, &x, &()).unwrap();
assert_eq!(val, 0.0);
}
#[test]
fn test_rosenbrock_log_density_increasing_cost_decreases_density() {
let f = Rosenbrock { n: 2 };
let x1 = Vector::<f64>::from_vec(vec![0.0, 0.0]); let x2 = Vector::<f64>::from_vec(vec![2.0, 2.0]); let ld1 = LogDensity::log_density(&f, &x1, &()).unwrap();
let ld2 = LogDensity::log_density(&f, &x2, &()).unwrap();
assert!(ld2 < ld1);
}
}