use crate::problem::{Bound, Problem};
pub const FEASIBLE_CEILING: f64 = 1e100;
#[inline]
fn infeasible_score(violation: f64) -> f64 {
FEASIBLE_CEILING * (1.0 + violation)
}
pub trait ConstrainedProblem {
fn dim(&self) -> usize;
fn bounds(&self) -> &[Bound];
fn objective(&self, x: &[f64]) -> f64;
fn violations(&self, x: &[f64]) -> Vec<f64>;
fn total_violation(&self, x: &[f64]) -> f64 {
self.violations(x).iter().map(|v| v.max(0.0)).sum()
}
}
pub struct DebRules<P>(pub P);
impl<P: ConstrainedProblem> Problem for DebRules<P> {
fn dim(&self) -> usize {
self.0.dim()
}
fn bounds(&self) -> &[Bound] {
self.0.bounds()
}
fn objective(&self, x: &[f64]) -> f64 {
let violation = self.0.total_violation(x);
if violation > 0.0 {
infeasible_score(violation)
} else {
self.0.objective(x)
}
}
}
pub fn constrained_func<F, G>(bounds: Vec<Bound>, f: F, g: G) -> ConstrainedFunc<F, G>
where
F: Fn(&[f64]) -> f64,
G: Fn(&[f64]) -> Vec<f64>,
{
ConstrainedFunc { bounds, f, g }
}
pub struct ConstrainedFunc<F, G> {
bounds: Vec<Bound>,
f: F,
g: G,
}
impl<F, G> ConstrainedProblem for ConstrainedFunc<F, G>
where
F: Fn(&[f64]) -> f64,
G: Fn(&[f64]) -> Vec<f64>,
{
fn dim(&self) -> usize {
self.bounds.len()
}
fn bounds(&self) -> &[Bound] {
&self.bounds
}
fn objective(&self, x: &[f64]) -> f64 {
(self.f)(x)
}
fn violations(&self, x: &[f64]) -> Vec<f64> {
(self.g)(x)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::algo::{LShade, Optimizer};
use crate::termination::Termination;
#[allow(clippy::type_complexity)]
fn ring() -> ConstrainedFunc<impl Fn(&[f64]) -> f64, impl Fn(&[f64]) -> Vec<f64>> {
constrained_func(
vec![(-2.0, 2.0); 2],
|x| x[0] * x[0] + x[1] * x[1],
|x| vec![(1.0 - x[0] - x[1]).max(0.0)],
)
}
#[test]
fn deb_rules_find_the_constrained_optimum() {
let report = LShade::default().optimize(&DebRules(ring()), &Termination::budget(8000));
assert!(
(report.best_value() - 0.5).abs() < 1e-3,
"got {}",
report.best_value()
);
assert!((report.best()[0] - 0.5).abs() < 0.03);
assert!((report.best()[1] - 0.5).abs() < 0.03);
}
#[test]
fn infeasible_ranks_by_violation_and_below_feasible() {
let p = DebRules(ring());
let feasible = p.objective(&[1.0, 1.0]); let mildly_infeasible = p.objective(&[0.4, 0.4]); let badly_infeasible = p.objective(&[-1.0, -1.0]); assert!(feasible < mildly_infeasible);
assert!(mildly_infeasible < badly_infeasible);
assert!(mildly_infeasible >= FEASIBLE_CEILING);
}
}