use std::{cmp::Ordering, fmt::Debug};
use serde::Serialize;
use crate::problems::objective::{IllegalObjective, Objective};
#[derive(Clone, Serialize, PartialEq)]
pub struct MultiObjective(Vec<f64>);
impl MultiObjective {
pub fn is_finite(&self) -> bool {
self.0.iter().all(|o| o.is_finite())
}
pub fn value(&self) -> &[f64] {
&self.0
}
}
impl Debug for MultiObjective {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self.0)
}
}
impl Eq for MultiObjective {}
impl PartialOrd for MultiObjective {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
if self == other {
return Some(Ordering::Equal);
}
if self.value().len() != other.value().len() {
return None;
}
let mut has_better = false;
let mut has_worse = false;
for (own, other) in self.value().iter().zip(other.value()) {
if own < other {
has_better = true;
} else if own > other {
has_worse = true;
}
}
match (has_better, has_worse) {
(true, false) => Some(Ordering::Less),
(false, true) => Some(Ordering::Greater),
_ => None,
}
}
}
impl Objective for MultiObjective {}
impl From<MultiObjective> for Vec<f64> {
fn from(objective: MultiObjective) -> Self {
objective.0
}
}
impl TryFrom<Vec<f64>> for MultiObjective {
type Error = IllegalObjective;
fn try_from(value: Vec<f64>) -> Result<Self, IllegalObjective> {
match value {
x if x.iter().any(|o| o.is_nan()) => Err(IllegalObjective::NaN),
x if x.iter().any(|o| o.is_infinite() && o.is_sign_negative()) => {
Err(IllegalObjective::NegativeInfinity)
}
_ => Ok(MultiObjective(value)),
}
}
}
impl TryFrom<&[f64]> for MultiObjective {
type Error = IllegalObjective;
fn try_from(value: &[f64]) -> Result<Self, IllegalObjective> {
match value {
x if x.iter().any(|o| o.is_nan()) => Err(IllegalObjective::NaN),
x if x.iter().any(|o| o.is_infinite() && o.is_sign_negative()) => {
Err(IllegalObjective::NegativeInfinity)
}
_ => Ok(MultiObjective(value.into())),
}
}
}
#[cfg(test)]
mod tests {
use std::cmp::Ordering;
use super::MultiObjective;
#[test]
fn is_finite_returns_true_for_all_finite() {
assert!(MultiObjective(vec![0., 0.]).is_finite());
assert!(MultiObjective(vec![-1., 1.]).is_finite());
}
#[test]
fn is_finite_returns_false_for_any_infinite() {
assert!(!MultiObjective(vec![0., f64::INFINITY]).is_finite());
assert!(!MultiObjective(vec![f64::INFINITY, f64::INFINITY]).is_finite());
}
#[test]
fn partial_ord_implements_pareto_domination() {
let a = MultiObjective(vec![0., 0.]);
let b = MultiObjective(vec![1., 1.]);
let c = MultiObjective(vec![1., 0.]);
let d = MultiObjective(vec![0., 1.]);
assert_eq!(a.partial_cmp(&a), Some(Ordering::Equal));
assert_eq!(b.partial_cmp(&b), Some(Ordering::Equal));
assert_eq!(c.partial_cmp(&c), Some(Ordering::Equal));
assert_eq!(d.partial_cmp(&d), Some(Ordering::Equal));
assert_eq!(a.partial_cmp(&b), Some(Ordering::Less));
assert_eq!(a.partial_cmp(&c), Some(Ordering::Less));
assert_eq!(a.partial_cmp(&d), Some(Ordering::Less));
assert_eq!(b.partial_cmp(&a), Some(Ordering::Greater));
assert_eq!(b.partial_cmp(&c), Some(Ordering::Greater));
assert_eq!(b.partial_cmp(&d), Some(Ordering::Greater));
assert_eq!(c.partial_cmp(&a), Some(Ordering::Greater));
assert_eq!(c.partial_cmp(&b), Some(Ordering::Less));
assert_eq!(c.partial_cmp(&d), None);
assert_eq!(d.partial_cmp(&a), Some(Ordering::Greater));
assert_eq!(d.partial_cmp(&b), Some(Ordering::Less));
assert_eq!(d.partial_cmp(&c), None);
}
#[test]
fn try_from_invalid_is_err() {
assert!(MultiObjective::try_from(vec![f64::NAN, 0.]).is_err());
assert!(MultiObjective::try_from(vec![f64::NAN, f64::NEG_INFINITY]).is_err());
assert!(MultiObjective::try_from(vec![f64::NEG_INFINITY, 0.]).is_err());
assert!(MultiObjective::try_from(vec![f64::NEG_INFINITY, f64::NEG_INFINITY]).is_err());
}
}