pub type Bound = (f64, f64);
pub trait Problem {
fn dim(&self) -> usize;
fn bounds(&self) -> &[Bound];
fn objective(&self, x: &[f64]) -> f64;
}
pub trait MultiProblem {
fn dim(&self) -> usize;
fn bounds(&self) -> &[Bound];
fn n_objectives(&self) -> usize;
fn objectives(&self, x: &[f64]) -> Vec<f64>;
}
pub fn multi_func<F>(bounds: Vec<Bound>, n_objectives: usize, f: F) -> MultiFunc<F>
where
F: Fn(&[f64]) -> Vec<f64>,
{
MultiFunc {
bounds,
n_objectives,
f,
}
}
pub struct MultiFunc<F> {
bounds: Vec<Bound>,
n_objectives: usize,
f: F,
}
impl<F> MultiProblem for MultiFunc<F>
where
F: Fn(&[f64]) -> Vec<f64>,
{
fn dim(&self) -> usize {
self.bounds.len()
}
fn bounds(&self) -> &[Bound] {
&self.bounds
}
fn n_objectives(&self) -> usize {
self.n_objectives
}
fn objectives(&self, x: &[f64]) -> Vec<f64> {
(self.f)(x)
}
}
pub fn validate(problem: &dyn Problem) -> Result<(), BoundsError> {
let b = problem.bounds();
if b.is_empty() || problem.dim() == 0 {
return Err(BoundsError::Empty);
}
if b.len() != problem.dim() {
return Err(BoundsError::DimMismatch {
dim: problem.dim(),
bounds: b.len(),
});
}
for (i, &(lo, hi)) in b.iter().enumerate() {
if lo.partial_cmp(&hi) != Some(std::cmp::Ordering::Less) {
return Err(BoundsError::NotOrdered { dim: i, lo, hi });
}
}
Ok(())
}
pub fn validate_multi(problem: &dyn MultiProblem) -> Result<(), BoundsError> {
struct AsProblem<'a>(&'a dyn MultiProblem);
impl Problem for AsProblem<'_> {
fn dim(&self) -> usize {
self.0.dim()
}
fn bounds(&self) -> &[Bound] {
self.0.bounds()
}
fn objective(&self, _x: &[f64]) -> f64 {
0.0
}
}
validate(&AsProblem(problem))?;
let m = problem.n_objectives();
if m < 2 {
return Err(BoundsError::BadObjectiveCount {
declared: m,
got: m,
});
}
let mid: Vec<f64> = problem
.bounds()
.iter()
.map(|&(lo, hi)| 0.5 * (lo + hi))
.collect();
let got = problem.objectives(&mid).len();
if got != m {
return Err(BoundsError::BadObjectiveCount { declared: m, got });
}
Ok(())
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum BoundsError {
Empty,
DimMismatch {
dim: usize,
bounds: usize,
},
NotOrdered {
dim: usize,
lo: f64,
hi: f64,
},
BadObjectiveCount {
declared: usize,
got: usize,
},
}
impl std::fmt::Display for BoundsError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BoundsError::Empty => write!(f, "problem must have at least one variable"),
BoundsError::DimMismatch { dim, bounds } => {
write!(f, "dim() = {dim} but bounds() has {bounds} entries")
}
BoundsError::NotOrdered { dim, lo, hi } => {
write!(
f,
"dimension {dim}: lower bound ({lo}) must be < upper bound ({hi})"
)
}
BoundsError::BadObjectiveCount { declared, got } => {
write!(
f,
"n_objectives() declares {declared} but objectives() returned {got} (need >= 2)"
)
}
}
}
}
impl std::error::Error for BoundsError {}
pub fn func<F>(bounds: Vec<Bound>, f: F) -> Func<F>
where
F: Fn(&[f64]) -> f64,
{
Func { bounds, f }
}
pub struct Func<F> {
bounds: Vec<Bound>,
f: F,
}
impl<F> Problem for Func<F>
where
F: Fn(&[f64]) -> f64,
{
fn dim(&self) -> usize {
self.bounds.len()
}
fn bounds(&self) -> &[Bound] {
&self.bounds
}
fn objective(&self, x: &[f64]) -> f64 {
(self.f)(x)
}
}
pub struct Maximize<P>(pub P);
impl<P: Problem> Problem for Maximize<P> {
fn dim(&self) -> usize {
self.0.dim()
}
fn bounds(&self) -> &[Bound] {
self.0.bounds()
}
fn objective(&self, x: &[f64]) -> f64 {
let v = self.0.objective(x);
if v.is_finite() {
-v
} else {
v
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn func_adapter_works() {
let p = func(vec![(-1.0, 1.0), (-1.0, 1.0)], |x| x[0] + x[1]);
assert_eq!(p.dim(), 2);
assert_eq!(p.objective(&[0.3, 0.4]), 0.7);
}
#[test]
fn maximize_negates_finite_only() {
let p = Maximize(func(vec![(0.0, 1.0)], |x| x[0]));
assert_eq!(p.objective(&[0.6]), -0.6);
let bad = Maximize(func(vec![(0.0, 1.0)], |_| f64::NAN));
assert!(bad.objective(&[0.5]).is_nan());
}
#[test]
fn validate_catches_bad_bounds() {
assert_eq!(
validate(&func(vec![], |_| 0.0)).unwrap_err(),
BoundsError::Empty
);
assert!(matches!(
validate(&func(vec![(1.0, 0.0)], |_| 0.0)).unwrap_err(),
BoundsError::NotOrdered { dim: 0, .. }
));
assert!(matches!(
validate(&func(vec![(0.0, f64::NAN)], |_| 0.0)).unwrap_err(),
BoundsError::NotOrdered { .. }
));
assert!(validate(&func(vec![(0.0, 1.0), (-2.0, 2.0)], |_| 0.0)).is_ok());
}
}