use basin::{
ConstrainedMadsState, CostFunction, Executor, Mads, MaxCostEvals,
NonlinearInequalityConstraints, TerminationReason,
};
struct Disk;
impl CostFunction for Disk {
type Param = Vec<f64>;
type Output = f64;
type Error = std::convert::Infallible;
fn cost(&self, x: &Vec<f64>) -> Result<f64, std::convert::Infallible> {
Ok(x[0] * x[1])
}
}
impl NonlinearInequalityConstraints for Disk {
fn constraints(&self, x: &Vec<f64>) -> Result<Vec<f64>, std::convert::Infallible> {
Ok(vec![x[0] * x[0] + x[1] * x[1] - 1.0])
}
fn num_constraints(&self) -> usize {
1
}
}
#[test]
fn converges_to_disk_optimum() {
let result = Executor::new(
Disk,
Mads::new()
.with_initial_poll_size(1.0)
.with_min_poll_size(1e-7)
.constrained(),
ConstrainedMadsState::new(vec![0.0, 0.0]),
)
.terminate_on(MaxCostEvals(20_000))
.run()
.unwrap();
assert_eq!(result.reason, TerminationReason::SolverConverged);
assert!(
(result.best_cost() - (-0.5)).abs() < 1e-2,
"f = {}",
result.best_cost()
);
let x = result.best_param();
assert!(x[0] * x[0] + x[1] * x[1] <= 1.0 + 1e-6, "infeasible {x:?}");
assert!(
result.state.constraint_violation() <= 1e-12,
"violation = {}",
result.state.constraint_violation()
);
}
#[test]
fn infeasible_start_reaches_feasible_optimum() {
let start = vec![2.0, -2.0]; let result = Executor::new(
Disk,
Mads::new()
.with_initial_poll_size(1.0)
.with_min_poll_size(1e-7)
.constrained(),
ConstrainedMadsState::new(start),
)
.terminate_on(MaxCostEvals(20_000))
.run()
.unwrap();
assert_eq!(result.reason, TerminationReason::SolverConverged);
let x = result.best_param();
assert!(
x[0] * x[0] + x[1] * x[1] <= 1.0 + 1e-6,
"did not reach feasibility: {x:?}"
);
assert!(
(result.best_cost() - (-0.5)).abs() < 1e-2,
"f = {}",
result.best_cost()
);
}
#[test]
fn respects_eval_budget() {
let result = Executor::new(
Disk,
Mads::new().constrained(),
ConstrainedMadsState::new(vec![0.0, 0.0]),
)
.terminate_on(MaxCostEvals(50))
.run()
.unwrap();
assert_eq!(result.reason, TerminationReason::MaxCostEvals);
}
#[cfg(feature = "nalgebra")]
#[test]
fn backend_generic_nalgebra() {
use nalgebra::DVector;
struct Disk;
impl CostFunction for Disk {
type Param = DVector<f64>;
type Output = f64;
type Error = std::convert::Infallible;
fn cost(&self, x: &DVector<f64>) -> Result<f64, std::convert::Infallible> {
Ok(x[0] * x[1])
}
}
impl NonlinearInequalityConstraints for Disk {
fn constraints(&self, x: &DVector<f64>) -> Result<DVector<f64>, std::convert::Infallible> {
Ok(DVector::from_vec(vec![x[0] * x[0] + x[1] * x[1] - 1.0]))
}
fn num_constraints(&self) -> usize {
1
}
}
let result = Executor::new(
Disk,
Mads::new().with_min_poll_size(1e-7).constrained(),
ConstrainedMadsState::new(DVector::from_vec(vec![0.0, 0.0])),
)
.terminate_on(MaxCostEvals(20_000))
.run()
.unwrap();
assert_eq!(result.reason, TerminationReason::SolverConverged);
assert!(
(result.best_cost() - (-0.5)).abs() < 1e-2,
"f = {}",
result.best_cost()
);
let x = result.best_param();
assert!(x[0] * x[0] + x[1] * x[1] <= 1.0 + 1e-6, "infeasible {x:?}");
}
#[cfg(feature = "ndarray")]
#[test]
fn backend_generic_ndarray() {
use ndarray::Array1;
struct Disk;
impl CostFunction for Disk {
type Param = Array1<f64>;
type Output = f64;
type Error = std::convert::Infallible;
fn cost(&self, x: &Array1<f64>) -> Result<f64, std::convert::Infallible> {
Ok(x[0] * x[1])
}
}
impl NonlinearInequalityConstraints for Disk {
fn constraints(&self, x: &Array1<f64>) -> Result<Array1<f64>, std::convert::Infallible> {
Ok(Array1::from_vec(vec![x[0] * x[0] + x[1] * x[1] - 1.0]))
}
fn num_constraints(&self) -> usize {
1
}
}
let result = Executor::new(
Disk,
Mads::new().with_min_poll_size(1e-7).constrained(),
ConstrainedMadsState::new(Array1::from_vec(vec![0.0, 0.0])),
)
.terminate_on(MaxCostEvals(20_000))
.run()
.unwrap();
assert_eq!(result.reason, TerminationReason::SolverConverged);
assert!(
(result.best_cost() - (-0.5)).abs() < 1e-2,
"f = {}",
result.best_cost()
);
let x = result.best_param();
assert!(x[0] * x[0] + x[1] * x[1] <= 1.0 + 1e-6, "infeasible {x:?}");
}
#[cfg(feature = "faer")]
#[test]
fn backend_generic_faer() {
use faer::Col;
struct Disk;
impl CostFunction for Disk {
type Param = Col<f64>;
type Output = f64;
type Error = std::convert::Infallible;
fn cost(&self, x: &Col<f64>) -> Result<f64, std::convert::Infallible> {
Ok(x[0] * x[1])
}
}
impl NonlinearInequalityConstraints for Disk {
fn constraints(&self, x: &Col<f64>) -> Result<Col<f64>, std::convert::Infallible> {
Ok(Col::from_fn(1, |_| x[0] * x[0] + x[1] * x[1] - 1.0))
}
fn num_constraints(&self) -> usize {
1
}
}
let result = Executor::new(
Disk,
Mads::new().with_min_poll_size(1e-7).constrained(),
ConstrainedMadsState::new(Col::from_fn(2, |_| 0.0)),
)
.terminate_on(MaxCostEvals(20_000))
.run()
.unwrap();
assert_eq!(result.reason, TerminationReason::SolverConverged);
assert!(
(result.best_cost() - (-0.5)).abs() < 1e-2,
"f = {}",
result.best_cost()
);
let x = result.best_param();
assert!(x[0] * x[0] + x[1] * x[1] <= 1.0 + 1e-6, "infeasible {x:?}");
}