use std::cmp::Ordering;
use hyperreal::Real;
use crate::{CandidateId, ReplayStatus};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum FitnessDirection {
Minimize,
Maximize,
}
#[derive(Clone, Debug, PartialEq)]
pub enum FitnessValue {
Scalar(Box<Real>),
Lexicographic(Vec<Real>),
Pareto(Vec<Real>),
Interval(Box<FitnessInterval>),
}
#[derive(Clone, Debug, PartialEq)]
pub struct FitnessInterval {
pub lower: Real,
pub upper: Real,
}
#[derive(Clone, Debug, PartialEq)]
pub struct FitnessReport {
pub candidate: CandidateId,
pub value: FitnessValue,
pub replay: ReplayStatus,
pub evidence: Vec<String>,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum FitnessComparison {
Better,
Worse,
Equal,
Unknown,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum FitnessIntervalComparison {
Better,
Worse,
Equal,
Overlapping,
InvalidBounds,
Unknown,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ParetoRelation {
Dominates,
Dominated,
NonDominated,
Equal,
Unknown,
}
impl FitnessValue {
pub fn compare_total(&self, other: &Self, direction: FitnessDirection) -> FitnessComparison {
match (self, other) {
(Self::Scalar(left), Self::Scalar(right)) => compare_real(left, right, direction),
(Self::Lexicographic(left), Self::Lexicographic(right)) => {
for (left, right) in left.iter().zip(right) {
let comparison = compare_real(left, right, direction);
if comparison != FitnessComparison::Equal {
return comparison;
}
}
match left.len().cmp(&right.len()) {
Ordering::Equal => FitnessComparison::Equal,
Ordering::Less => FitnessComparison::Worse,
Ordering::Greater => FitnessComparison::Better,
}
}
(Self::Interval(left), Self::Interval(right)) => match left.compare(right, direction) {
FitnessIntervalComparison::Better => FitnessComparison::Better,
FitnessIntervalComparison::Worse => FitnessComparison::Worse,
FitnessIntervalComparison::Equal => FitnessComparison::Equal,
FitnessIntervalComparison::Overlapping
| FitnessIntervalComparison::InvalidBounds
| FitnessIntervalComparison::Unknown => FitnessComparison::Unknown,
},
_ => FitnessComparison::Unknown,
}
}
pub fn compare_pareto(&self, other: &Self, direction: FitnessDirection) -> ParetoRelation {
let (Self::Pareto(left), Self::Pareto(right)) = (self, other) else {
return ParetoRelation::Unknown;
};
if left.len() != right.len() {
return ParetoRelation::Unknown;
}
let mut better = false;
let mut worse = false;
for (left, right) in left.iter().zip(right) {
match compare_real(left, right, direction) {
FitnessComparison::Better => better = true,
FitnessComparison::Worse => worse = true,
FitnessComparison::Equal => {}
FitnessComparison::Unknown => return ParetoRelation::Unknown,
}
}
match (better, worse) {
(false, false) => ParetoRelation::Equal,
(true, false) => ParetoRelation::Dominates,
(false, true) => ParetoRelation::Dominated,
(true, true) => ParetoRelation::NonDominated,
}
}
}
impl FitnessInterval {
pub fn new(lower: Real, upper: Real) -> Self {
Self { lower, upper }
}
pub fn has_valid_bounds(&self) -> Option<bool> {
match self.lower.partial_cmp(&self.upper) {
Some(Ordering::Less | Ordering::Equal) => Some(true),
Some(Ordering::Greater) => Some(false),
None => None,
}
}
pub fn compare(&self, other: &Self, direction: FitnessDirection) -> FitnessIntervalComparison {
match (self.has_valid_bounds(), other.has_valid_bounds()) {
(Some(true), Some(true)) => {}
(Some(false), _) | (_, Some(false)) => {
return FitnessIntervalComparison::InvalidBounds;
}
(None, _) | (_, None) => return FitnessIntervalComparison::Unknown,
}
if self.lower == other.lower && self.upper == other.upper {
return FitnessIntervalComparison::Equal;
}
match direction {
FitnessDirection::Minimize => {
if matches!(self.upper.partial_cmp(&other.lower), Some(Ordering::Less)) {
FitnessIntervalComparison::Better
} else if matches!(
self.lower.partial_cmp(&other.upper),
Some(Ordering::Greater)
) {
FitnessIntervalComparison::Worse
} else if self.upper.partial_cmp(&other.lower).is_none()
|| self.lower.partial_cmp(&other.upper).is_none()
{
FitnessIntervalComparison::Unknown
} else {
FitnessIntervalComparison::Overlapping
}
}
FitnessDirection::Maximize => {
if matches!(
self.lower.partial_cmp(&other.upper),
Some(Ordering::Greater)
) {
FitnessIntervalComparison::Better
} else if matches!(self.upper.partial_cmp(&other.lower), Some(Ordering::Less)) {
FitnessIntervalComparison::Worse
} else if self.lower.partial_cmp(&other.upper).is_none()
|| self.upper.partial_cmp(&other.lower).is_none()
{
FitnessIntervalComparison::Unknown
} else {
FitnessIntervalComparison::Overlapping
}
}
}
}
}
fn compare_real(left: &Real, right: &Real, direction: FitnessDirection) -> FitnessComparison {
match left.partial_cmp(right) {
Some(Ordering::Less) => match direction {
FitnessDirection::Minimize => FitnessComparison::Better,
FitnessDirection::Maximize => FitnessComparison::Worse,
},
Some(Ordering::Greater) => match direction {
FitnessDirection::Minimize => FitnessComparison::Worse,
FitnessDirection::Maximize => FitnessComparison::Better,
},
Some(Ordering::Equal) => FitnessComparison::Equal,
None => FitnessComparison::Unknown,
}
}