use std::cmp::Ordering;
use crate::core::{Individual, OError};
#[derive(Debug, PartialOrd, PartialEq)]
pub enum PreferredSolution {
First,
Second,
MutuallyPreferred,
}
pub trait BinaryComparisonOperator {
fn compare(
first_solution: &Individual,
second_solution: &Individual,
) -> Result<PreferredSolution, OError>
where
Self: Sized;
}
pub struct ParetoConstrainedDominance;
impl BinaryComparisonOperator for ParetoConstrainedDominance {
fn compare(
first_solution: &Individual,
second_solution: &Individual,
) -> Result<PreferredSolution, OError> {
let problem = first_solution.problem();
let cv1 = first_solution.constraint_violation();
let cv2 = second_solution.constraint_violation();
if problem.number_of_constraints() > 0 && cv1 != cv2 {
if first_solution.is_feasible() {
return Ok(PreferredSolution::First);
} else if second_solution.is_feasible() {
return Ok(PreferredSolution::Second);
} else if cv1 < cv2 {
return Ok(PreferredSolution::First);
} else if cv1 > cv2 {
return Ok(PreferredSolution::Second);
}
}
let mut relation = PreferredSolution::MutuallyPreferred;
for objective_name in problem.objective_names() {
let obj_sol1 = first_solution.get_objective_value(objective_name.as_str())?;
let obj_sol2 = second_solution.get_objective_value(objective_name.as_str())?;
if obj_sol1 < obj_sol2 {
if relation == PreferredSolution::Second {
return Ok(PreferredSolution::MutuallyPreferred);
}
relation = PreferredSolution::First;
} else if obj_sol1 > obj_sol2 {
if relation == PreferredSolution::First {
return Ok(PreferredSolution::MutuallyPreferred);
}
relation = PreferredSolution::Second;
}
}
Ok(relation)
}
}
pub struct CrowdedComparison;
impl BinaryComparisonOperator for CrowdedComparison {
fn compare(
first_solution: &Individual,
second_solution: &Individual,
) -> Result<PreferredSolution, OError> {
let name = "CrowdedComparison".to_string();
let rank1 = match first_solution.get_data("rank") {
Err(_) => {
return Err(OError::ComparisonOperator(
name,
"The rank on the first individual does not exist".to_string(),
))
}
Ok(r) => r.as_integer()?,
};
let rank2 = match second_solution.get_data("rank") {
Err(_) => {
return Err(OError::ComparisonOperator(
name,
"The rank on the second individual does not exist".to_string(),
))
}
Ok(r) => r.as_integer()?,
};
match rank1.cmp(&rank2) {
Ordering::Less => Ok(PreferredSolution::First),
Ordering::Equal => {
let d1 = match first_solution.get_data("crowding_distance") {
Err(_) => {
return Err(OError::ComparisonOperator(
name,
format!(
"The crowding distance on the first individual {:?} does not exist",
first_solution.variables()
),
))
}
Ok(r) => r.as_real()?,
};
let d2 = match second_solution.get_data("crowding_distance") {
Err(_) => {
return Err(OError::ComparisonOperator(
name,
format!(
"The crowding distance on the second individual {:?} does not exist",
second_solution.variables()
),
))
}
Ok(r) => r.as_real()?,
};
if d1 > d2 {
Ok(PreferredSolution::First)
} else {
Ok(PreferredSolution::Second)
}
}
Ordering::Greater => Ok(PreferredSolution::Second),
}
}
}
#[cfg(test)]
mod test_pareto_constrained_dominance {
use std::sync::Arc;
use crate::core::utils::dummy_evaluator;
use crate::core::{
BoundedNumber, Constraint, Individual, Objective, ObjectiveDirection, Problem,
RelationalOperator, VariableType,
};
use crate::operators::{
BinaryComparisonOperator, ParetoConstrainedDominance, PreferredSolution,
};
#[test]
fn test_unconstrained_solutions_1_objective() {
let objectives = vec![Objective::new("obj1", ObjectiveDirection::Minimise)];
let variables = vec![VariableType::Real(
BoundedNumber::new("X1", 0.0, 2.0).unwrap(),
)];
let e = dummy_evaluator();
let problem = Arc::new(Problem::new(objectives, variables, None, e).unwrap());
let mut solution1 = Individual::new(problem.clone());
let mut solution2 = Individual::new(problem.clone());
solution1.update_objective("obj1", 5.0).unwrap();
solution2.update_objective("obj1", 15.0).unwrap();
assert_eq!(
ParetoConstrainedDominance::compare(&solution1, &solution2).unwrap(),
PreferredSolution::First
);
solution1.update_objective("obj1", 5.0).unwrap();
solution2.update_objective("obj1", 1.0).unwrap();
assert_eq!(
ParetoConstrainedDominance::compare(&solution1, &solution2).unwrap(),
PreferredSolution::Second
);
solution1.update_objective("obj1", 5.0).unwrap();
solution2.update_objective("obj1", 5.0).unwrap();
assert_eq!(
ParetoConstrainedDominance::compare(&solution1, &solution2).unwrap(),
PreferredSolution::MutuallyPreferred
);
let objectives = vec![Objective::new("obj1", ObjectiveDirection::Maximise)];
let variables = vec![VariableType::Real(
BoundedNumber::new("X1", 0.0, 2.0).unwrap(),
)];
let e = dummy_evaluator();
let problem = Arc::new(Problem::new(objectives, variables, None, e).unwrap());
let mut solution1 = Individual::new(problem.clone());
let mut solution2 = Individual::new(problem.clone());
solution1.update_objective("obj1", 5.0).unwrap();
solution2.update_objective("obj1", 15.0).unwrap();
assert_eq!(
ParetoConstrainedDominance::compare(&solution1, &solution2).unwrap(),
PreferredSolution::Second
);
}
#[test]
fn test_unconstrained_solutions_2_objectives() {
let objectives = vec![
Objective::new("obj1", ObjectiveDirection::Minimise),
Objective::new("obj2", ObjectiveDirection::Minimise),
];
let variables = vec![VariableType::Real(
BoundedNumber::new("X1", 0.0, 2.0).unwrap(),
)];
let e = dummy_evaluator();
let problem = Arc::new(Problem::new(objectives, variables, None, e).unwrap());
let mut solution1 = Individual::new(problem.clone());
let mut solution2 = Individual::new(problem.clone());
solution1.update_objective("obj1", 5.0).unwrap();
solution1.update_objective("obj2", 1.0).unwrap();
solution2.update_objective("obj1", 15.0).unwrap();
solution2.update_objective("obj2", 25.0).unwrap();
assert_eq!(
ParetoConstrainedDominance::compare(&solution1, &solution2).unwrap(),
PreferredSolution::First
);
solution1.update_objective("obj1", 5.0).unwrap();
solution1.update_objective("obj2", 1.0).unwrap();
solution2.update_objective("obj1", -15.0).unwrap();
solution2.update_objective("obj2", -25.0).unwrap();
assert_eq!(
ParetoConstrainedDominance::compare(&solution1, &solution2).unwrap(),
PreferredSolution::Second
);
solution1.update_objective("obj1", 5.0).unwrap();
solution1.update_objective("obj2", 100.0).unwrap();
solution2.update_objective("obj1", 15.0).unwrap();
solution2.update_objective("obj2", 25.0).unwrap();
assert_eq!(
ParetoConstrainedDominance::compare(&solution1, &solution2).unwrap(),
PreferredSolution::MutuallyPreferred
);
let mut solution3 = Individual::new(problem.clone());
solution1.update_objective("obj1", 0.0).unwrap();
solution1.update_objective("obj2", 0.0).unwrap();
solution2.update_objective("obj1", 1.0).unwrap();
solution2.update_objective("obj2", 1.0).unwrap();
solution3.update_objective("obj1", 0.0).unwrap();
solution3.update_objective("obj2", 1.0).unwrap();
assert_eq!(
ParetoConstrainedDominance::compare(&solution1, &solution2).unwrap(),
PreferredSolution::First
);
assert_eq!(
ParetoConstrainedDominance::compare(&solution2, &solution1).unwrap(),
PreferredSolution::Second
);
assert_eq!(
ParetoConstrainedDominance::compare(&solution1, &solution3).unwrap(),
PreferredSolution::First
);
assert_eq!(
ParetoConstrainedDominance::compare(&solution3, &solution1).unwrap(),
PreferredSolution::Second
);
solution1.update_objective("obj1", 0.0).unwrap();
solution1.update_objective("obj2", 1.0).unwrap();
solution2.update_objective("obj1", 0.5).unwrap();
solution2.update_objective("obj2", 0.5).unwrap();
solution3.update_objective("obj1", 1.0).unwrap();
solution3.update_objective("obj2", 0.0).unwrap();
assert_eq!(
ParetoConstrainedDominance::compare(&solution1, &solution2).unwrap(),
PreferredSolution::MutuallyPreferred
);
assert_eq!(
ParetoConstrainedDominance::compare(&solution2, &solution1).unwrap(),
PreferredSolution::MutuallyPreferred
);
assert_eq!(
ParetoConstrainedDominance::compare(&solution2, &solution3).unwrap(),
PreferredSolution::MutuallyPreferred
);
assert_eq!(
ParetoConstrainedDominance::compare(&solution3, &solution2).unwrap(),
PreferredSolution::MutuallyPreferred
);
assert_eq!(
ParetoConstrainedDominance::compare(&solution1, &solution3).unwrap(),
PreferredSolution::MutuallyPreferred
);
assert_eq!(
ParetoConstrainedDominance::compare(&solution3, &solution1).unwrap(),
PreferredSolution::MutuallyPreferred
);
let objectives = vec![
Objective::new("obj1", ObjectiveDirection::Minimise),
Objective::new("obj2", ObjectiveDirection::Maximise),
];
let variables = vec![VariableType::Real(
BoundedNumber::new("X1", 0.0, 2.0).unwrap(),
)];
let e = dummy_evaluator();
let problem = Arc::new(Problem::new(objectives, variables, None, e).unwrap());
let mut solution1 = Individual::new(problem.clone());
let mut solution2 = Individual::new(problem.clone());
solution1.update_objective("obj1", 5.0).unwrap();
solution2.update_objective("obj1", 15.0).unwrap();
solution1.update_objective("obj2", 5.0).unwrap();
solution2.update_objective("obj2", 15.0).unwrap();
assert_eq!(
ParetoConstrainedDominance::compare(&solution1, &solution2).unwrap(),
PreferredSolution::MutuallyPreferred
);
solution1.update_objective("obj1", 5.0).unwrap();
solution1.update_objective("obj2", -5.0).unwrap();
solution2.update_objective("obj1", 1.0).unwrap();
solution2.update_objective("obj2", 15.0).unwrap();
assert_eq!(
ParetoConstrainedDominance::compare(&solution1, &solution2).unwrap(),
PreferredSolution::Second
);
}
#[test]
fn test_constrained_solutions() {
let objectives = vec![Objective::new("obj1", ObjectiveDirection::Minimise)];
let variables = vec![VariableType::Real(
BoundedNumber::new("X1", 0.0, 2.0).unwrap(),
)];
let constraints = vec![Constraint::new("c1", RelationalOperator::EqualTo, 1.0)];
let e = dummy_evaluator();
let problem = Arc::new(Problem::new(objectives, variables, Some(constraints), e).unwrap());
let mut solution1 = Individual::new(problem.clone());
let mut solution2 = Individual::new(problem.clone());
solution1.update_objective("obj1", 5.0).unwrap();
solution2.update_objective("obj1", 15.0).unwrap();
solution1.update_constraint("c1", 0.0).unwrap();
solution2.update_constraint("c1", 1.0).unwrap();
assert_eq!(
ParetoConstrainedDominance::compare(&solution1, &solution2).unwrap(),
PreferredSolution::Second
);
solution1.update_constraint("c1", 0.5).unwrap();
solution2.update_constraint("c1", 3.0).unwrap();
assert_eq!(
ParetoConstrainedDominance::compare(&solution1, &solution2).unwrap(),
PreferredSolution::First
);
solution1.update_constraint("c1", 0.5).unwrap();
solution2.update_constraint("c1", 0.5).unwrap();
assert_eq!(
ParetoConstrainedDominance::compare(&solution1, &solution2).unwrap(),
PreferredSolution::First
);
let objectives = vec![
Objective::new("obj1", ObjectiveDirection::Minimise),
Objective::new("obj2", ObjectiveDirection::Minimise),
];
let variables = vec![VariableType::Real(
BoundedNumber::new("X1", 0.0, 2.0).unwrap(),
)];
let constraints = vec![Constraint::new("c1", RelationalOperator::EqualTo, 5.0)];
let e = dummy_evaluator();
let problem2 = Arc::new(Problem::new(objectives, variables, Some(constraints), e).unwrap());
let mut solution1 = Individual::new(problem2.clone());
let mut solution2 = Individual::new(problem2);
solution1.update_objective("obj2", 100.0).unwrap();
solution2.update_objective("obj2", 15.0).unwrap();
solution1.update_constraint("c1", 0.5).unwrap();
solution2.update_constraint("c1", 3.0).unwrap();
assert_eq!(
ParetoConstrainedDominance::compare(&solution1, &solution2).unwrap(),
PreferredSolution::Second
);
}
}
#[cfg(test)]
mod test_crowded_comparison {
use std::sync::Arc;
use crate::core::utils::dummy_evaluator;
use crate::core::{
BoundedNumber, DataValue, Individual, Objective, ObjectiveDirection, Problem, VariableType,
};
use crate::operators::comparison::CrowdedComparison;
use crate::operators::{BinaryComparisonOperator, PreferredSolution};
#[test]
fn test_different_rank() {
let objectives = vec![Objective::new("obj1", ObjectiveDirection::Minimise)];
let variables = vec![VariableType::Real(
BoundedNumber::new("X1", 0.0, 2.0).unwrap(),
)];
let e = dummy_evaluator();
let problem = Arc::new(Problem::new(objectives, variables, None, e).unwrap());
let mut solution1 = Individual::new(problem.clone());
let mut solution2 = Individual::new(problem.clone());
solution1.set_data("rank", DataValue::Integer(1));
solution2.set_data("rank", DataValue::Integer(4));
assert_eq!(
CrowdedComparison::compare(&solution1, &solution2).unwrap(),
PreferredSolution::First
);
solution1.set_data("rank", DataValue::Integer(5));
assert_eq!(
CrowdedComparison::compare(&solution1, &solution2).unwrap(),
PreferredSolution::Second
);
}
#[test]
fn test_same_rank() {
let objectives = vec![Objective::new("obj1", ObjectiveDirection::Minimise)];
let variables = vec![VariableType::Real(
BoundedNumber::new("X1", 0.0, 2.0).unwrap(),
)];
let e = dummy_evaluator();
let problem = Arc::new(Problem::new(objectives, variables, None, e).unwrap());
let mut solution1 = Individual::new(problem.clone());
let mut solution2 = Individual::new(problem.clone());
solution1.set_data("rank", DataValue::Integer(1));
solution2.set_data("rank", DataValue::Integer(1));
solution1.set_data("crowding_distance", DataValue::Real(10.5));
solution2.set_data("crowding_distance", DataValue::Real(0.32));
assert_eq!(
CrowdedComparison::compare(&solution1, &solution2).unwrap(),
PreferredSolution::First
);
solution2.set_data("crowding_distance", DataValue::Real(100.32));
assert_eq!(
CrowdedComparison::compare(&solution1, &solution2).unwrap(),
PreferredSolution::Second
);
}
}