use crate::errors::QlResult;
use crate::fail;
use crate::math::array::Array;
use crate::math::optimization::endcriteria::{EndCriteria, EndCriteriaType};
use crate::math::optimization::method::OptimizationMethod;
use crate::math::optimization::problem::Problem;
use crate::require;
use crate::types::{Real, Size};
fn compute_simplex_size(vertices: &[Array]) -> Real {
let mut center = Array::with_size(vertices[0].size());
for vertex in vertices {
for (c, v) in center.iter_mut().zip(vertex.iter()) {
*c += v;
}
}
let scale = 1.0 / vertices.len() as Real;
for c in center.iter_mut() {
*c *= scale;
}
let mut result = 0.0;
for vertex in vertices {
let distance2: Real = vertex
.iter()
.zip(center.iter())
.map(|(v, c)| (v - c) * (v - c))
.sum();
result += distance2.sqrt();
}
result / vertices.len() as Real
}
pub struct Simplex {
lambda: Real,
vertices: Vec<Array>,
values: Array,
sum: Array,
}
impl Simplex {
pub fn new(lambda: Real) -> Self {
assert!(
lambda.is_finite() && lambda > 0.0,
"lambda ({lambda}) must be finite and positive"
);
Simplex {
lambda,
vertices: Vec::new(),
values: Array::new(),
sum: Array::new(),
}
}
pub fn lambda(&self) -> Real {
self.lambda
}
fn extrapolate(
&mut self,
problem: &mut Problem<'_>,
i_highest: Size,
factor: &mut Real,
) -> Real {
let mut p_try;
loop {
let dimensions = self.values.size() - 1;
let factor1 = (1.0 - *factor) / dimensions as Real;
let factor2 = factor1 - *factor;
p_try = &(&self.sum * factor1) - &(&self.vertices[i_highest] * factor2);
*factor *= 0.5;
if problem.constraint().test(&p_try) || factor.abs() <= Real::EPSILON {
break;
}
}
if factor.abs() <= Real::EPSILON {
return self.values[i_highest];
}
*factor *= 2.0;
let v_try = problem.value(&p_try);
if v_try < self.values[i_highest] {
self.values[i_highest] = v_try;
self.sum = &(&self.sum + &p_try) - &self.vertices[i_highest];
self.vertices[i_highest] = p_try;
}
v_try
}
}
impl OptimizationMethod for Simplex {
fn minimize(
&mut self,
problem: &mut Problem<'_>,
end_criteria: &EndCriteria,
) -> QlResult<EndCriteriaType> {
let xtol = end_criteria.root_epsilon();
let mut max_stationary_state_iterations = end_criteria.max_stationary_state_iterations();
let mut ec_type = EndCriteriaType::None;
problem.reset();
let mut x = problem.current_value().clone();
if !problem.constraint().test(&x) {
fail!("initial guess {x:?} is not in the feasible region");
}
let mut iteration_number: Size = 0;
let n = x.size();
require!(n > 0, "no variables given");
self.vertices = vec![x.clone(); n + 1];
for i in 0..n {
let mut direction = Array::with_size(n);
direction[i] = 1.0;
problem
.constraint()
.update(&mut self.vertices[i + 1], &direction, self.lambda)?;
}
self.values = Array::with_size(n + 1);
for i in 0..=n {
self.values[i] = problem.value(&self.vertices[i]);
}
loop {
self.sum = Array::with_size(n);
for vertex in &self.vertices {
for (s, v) in self.sum.iter_mut().zip(vertex.iter()) {
*s += v;
}
}
let mut i_lowest = 0;
let (mut i_highest, mut i_next_highest) = if self.values[0] < self.values[1] {
(1, 0)
} else {
(0, 1)
};
for i in 1..=n {
if self.values[i] > self.values[i_highest] {
i_next_highest = i_highest;
i_highest = i;
} else if self.values[i] > self.values[i_next_highest] && i != i_highest {
i_next_highest = i;
}
if self.values[i] < self.values[i_lowest] {
i_lowest = i;
}
}
let simplex_size = compute_simplex_size(&self.vertices);
iteration_number += 1;
if simplex_size < xtol
|| end_criteria.check_max_iterations(iteration_number, &mut ec_type)
{
end_criteria.check_stationary_point(
0.0,
0.0,
&mut max_stationary_state_iterations,
&mut ec_type,
);
end_criteria.check_max_iterations(iteration_number, &mut ec_type);
x = self.vertices[i_lowest].clone();
let low = self.values[i_lowest];
problem.set_function_value(low);
problem.set_current_value(x);
return Ok(ec_type);
}
let mut factor = -1.0;
let mut v_try = self.extrapolate(problem, i_highest, &mut factor);
if v_try <= self.values[i_lowest] && factor == -1.0 {
factor = 2.0;
self.extrapolate(problem, i_highest, &mut factor);
} else if factor.abs() > Real::EPSILON && v_try >= self.values[i_next_highest] {
let v_save = self.values[i_highest];
factor = 0.5;
v_try = self.extrapolate(problem, i_highest, &mut factor);
if v_try >= v_save && factor.abs() > Real::EPSILON {
for i in 0..=n {
if i != i_lowest {
self.vertices[i] =
&(&self.vertices[i] + &self.vertices[i_lowest]) * 0.5;
self.values[i] = problem.value(&self.vertices[i]);
}
}
}
}
if factor.abs() <= Real::EPSILON {
x = self.vertices[i_lowest].clone();
let low = self.values[i_lowest];
problem.set_function_value(low);
problem.set_current_value(x);
return Ok(EndCriteriaType::StationaryFunctionValue);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::math::optimization::testsupport::check_parabola_minimization;
#[test]
fn minimizes_one_dimensional_parabola() {
check_parabola_minimization(&mut Simplex::new(0.1), "Simplex");
}
#[test]
#[should_panic(expected = "lambda (0) must be finite and positive")]
fn rejects_a_zero_lambda() {
let _ = Simplex::new(0.0);
}
#[test]
#[should_panic(expected = "lambda (-0.1) must be finite and positive")]
fn rejects_a_negative_lambda() {
let _ = Simplex::new(-0.1);
}
#[test]
#[should_panic(expected = "lambda (NaN) must be finite and positive")]
fn rejects_a_non_finite_lambda() {
let _ = Simplex::new(Real::NAN);
}
#[test]
fn rejects_an_empty_parameter_vector() {
use crate::math::optimization::constraint::NoConstraint;
use crate::math::optimization::costfunction::CostFunction;
struct AnyCost;
impl CostFunction for AnyCost {
fn values(&self, x: &Array) -> Array {
x.clone()
}
}
let cost = AnyCost;
let constraint = NoConstraint;
let mut problem = Problem::new(&cost, &constraint, Array::new());
let end_criteria = EndCriteria::new(100, Some(10), 1e-8, 1e-8, None).unwrap();
let err = Simplex::new(0.1)
.minimize(&mut problem, &end_criteria)
.unwrap_err();
assert_eq!(err.message(), "no variables given");
}
}