use std::convert::Infallible;
use std::ops::{Index, IndexMut};
use basin::core::math::{Scalar, VectorLen};
use basin::{Bobyqa, CostFunction, Executor, PointState, TerminationCode};
struct Corner<V> {
lower: V,
upper: V,
coefficients: V,
}
impl<V, F> basin::ProblemConstraints for &Corner<V>
where
V: VectorLen + Index<usize, Output = F>,
F: Scalar,
{
type Constraints = basin::Constraints<basin::BoxBounds<V>>;
fn constraint_set(
&self,
) -> <Self::Constraints as basin::ConstraintDescription>::Borrowed<'_> {
basin::Constraints::new().with_bounds(basin::BoxBounds::new(
Some(&self.lower),
Some(&self.upper),
))
}
}
impl<V, F> CostFunction for &Corner<V>
where
V: VectorLen + Index<usize, Output = F>,
F: Scalar,
{
type Param = V;
type Output = F;
type Error = Infallible;
fn cost(&self, x: &V) -> Result<F, Infallible> {
assert!((0..x.vec_len()).all(|i| {
x[i].is_finite() && self.lower[i] <= x[i] && x[i] <= self.upper[i]
}));
Ok((0..x.vec_len()).map(|i| self.coefficients[i] * x[i]).sum())
}
}
fn check_corners<V, F>(from_slice: impl Fn(&[f64]) -> V)
where
V: Clone + VectorLen + IndexMut<usize, Output = F>,
F: Scalar,
{
for (start, coefficients) in [
([0.0, 0.0, 1.0, 1.0], [-1.0; 4]),
([1.0, 1.0, 0.0, 0.0], [1.0; 4]),
([0.0, 1.0, 0.0, 1.0], [1.0, -1.0, -1.0, 1.0]),
([1.0; 4], [-1.0; 4]),
] {
let problem = Corner {
lower: from_slice(&[0.0; 4]),
upper: from_slice(&[1.0; 4]),
coefficients: from_slice(&coefficients),
};
let solver = Bobyqa::new()
.with_initial_radius(F::from_f64(0.05).unwrap())
.with_final_radius(F::from_f64(5e-5).unwrap());
let result = Executor::new(
&problem,
solver,
PointState::new(from_slice(&start)),
)
.max_cost_evals(300)
.run()
.unwrap();
let tolerance = F::from_f64(1e-6).unwrap();
assert_eq!(result.report.code(), TerminationCode::SolverConverged);
for (i, &coefficient) in coefficients.iter().enumerate() {
let expected = if coefficient < 0.0 {
F::one()
} else {
F::zero()
};
assert!((result.best_param()[i] - expected).abs() < tolerance);
}
let expected_cost = coefficients.iter().map(|c| c.min(0.0)).sum();
assert!(
(result.best_cost() - F::from_f64(expected_cost).unwrap()).abs()
< tolerance
);
assert_eq!(
(&problem).cost(result.best_param()).unwrap(),
result.best_cost()
);
}
}
#[test]
fn linear_objective_reaches_box_corner_vec() {
check_corners::<_, f64>(|x| x.to_vec());
}
#[test]
fn linear_objective_reaches_box_corner_f32() {
check_corners::<_, f32>(|x| {
x.iter().map(|&v| v as f32).collect::<Vec<_>>()
});
}
#[cfg(feature = "nalgebra_all")]
#[test]
fn linear_objective_reaches_box_corner_nalgebra() {
check_corners::<_, f64>(
backend_aliases::nalgebra::DVector::from_column_slice,
);
}
#[cfg(feature = "ndarray_all")]
#[test]
fn linear_objective_reaches_box_corner_ndarray() {
check_corners::<_, f64>(|x| {
backend_aliases::ndarray::Array1::from(x.to_vec())
});
}
#[cfg(feature = "faer_all")]
#[test]
fn linear_objective_reaches_box_corner_faer() {
check_corners::<_, f64>(|x| {
backend_aliases::faer::Col::from_fn(x.len(), |i| x[i])
});
}
#[path = "support/backend_aliases.rs"]
mod backend_aliases;