use crate::core::utils::dummy_evaluator;
use crate::core::{Constraint, Individual, OError, Objective, ObjectiveDirection, VariableType};
use crate::utils::has_unique_elements_by_key;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::error::Error;
use std::fmt::{Debug, Display, Formatter};
#[cfg(feature = "python")]
use crate::core::PyVariable;
#[cfg(feature = "python")]
use pyo3::prelude::*;
#[cfg(feature = "python")]
use pyo3::types::PyDict;
#[derive(Debug)]
pub struct EvaluationResult {
pub constraints: Option<HashMap<String, f64>>,
pub objectives: HashMap<String, f64>,
}
pub trait Evaluator: Sync + Send + Debug {
fn evaluate(&self, individual: &Individual) -> Result<EvaluationResult, Box<dyn Error>>;
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ProblemExport {
pub objectives: HashMap<String, Objective>,
pub constraints: HashMap<String, Constraint>,
pub variables: HashMap<String, VariableType>,
pub constraint_names: Vec<String>,
pub variable_names: Vec<String>,
pub objective_names: Vec<String>,
pub number_of_objectives: usize,
pub number_of_constraints: usize,
pub number_of_variables: usize,
}
impl TryInto<Problem> for ProblemExport {
type Error = OError;
fn try_into(self) -> Result<Problem, Self::Error> {
let objectives = self.objectives.values().cloned().collect();
let variables = self.variables.values().cloned().collect();
let constraints = self.constraints.values().cloned().collect();
Problem::new(objectives, variables, Some(constraints), dummy_evaluator())
}
}
#[derive(Debug)]
pub struct Problem {
objectives: Vec<Objective>,
constraints: Vec<Constraint>,
variables: Vec<VariableType>,
evaluator: Box<dyn Evaluator>,
}
impl Display for Problem {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Problem with {} variables, {} objectives and {} constraints",
self.number_of_variables(),
self.number_of_objectives(),
self.number_of_constraints(),
)
}
}
impl Problem {
pub fn new(
objectives: Vec<Objective>,
variable_types: Vec<VariableType>,
constraints: Option<Vec<Constraint>>,
evaluator: Box<dyn Evaluator>,
) -> Result<Self, OError> {
if objectives.is_empty() {
return Err(OError::NoObjective);
}
if variable_types.is_empty() {
return Err(OError::NoVariables);
}
if !has_unique_elements_by_key(&objectives, |o| o.name()) {
return Err(OError::DuplicatedName("objective".to_string()));
}
if !has_unique_elements_by_key(&variable_types, |v| v.name()) {
return Err(OError::DuplicatedName("variable".to_string()));
}
let constraints = constraints.unwrap_or_default();
if !has_unique_elements_by_key(&constraints, |c| c.name()) {
return Err(OError::DuplicatedName("constraint".to_string()));
}
Ok(Self {
variables: variable_types,
objectives,
constraints,
evaluator,
})
}
pub fn is_objective_minimised(&self, name: &str) -> Result<bool, OError> {
match self.objectives.iter().position(|o| o.name() == name) {
None => Err(OError::NonExistingName(
"objective".to_string(),
name.to_string(),
)),
Some(p) => Ok(self.objectives[p].direction() == ObjectiveDirection::Minimise),
}
}
pub fn number_of_objectives(&self) -> usize {
self.objectives.len()
}
pub fn number_of_constraints(&self) -> usize {
self.constraints.len()
}
pub fn number_of_variables(&self) -> usize {
self.variables.len()
}
pub fn variable_names(&self) -> Vec<String> {
self.variables.iter().map(|o| o.name()).collect()
}
pub fn objective_names(&self) -> Vec<String> {
self.objectives.iter().map(|o| o.name()).collect()
}
pub fn constraint_names(&self) -> Vec<String> {
self.constraints.iter().map(|o| o.name()).collect()
}
pub fn variables(&self) -> Vec<(String, VariableType)> {
self.variables
.iter()
.map(|o| (o.name().clone(), o.clone()))
.collect()
}
pub fn get_variable(&self, name: &str) -> Result<VariableType, OError> {
match self.variables.iter().position(|o| o.name() == name) {
None => Err(OError::NonExistingName(
"variable".to_string(),
name.to_string(),
)),
Some(p) => Ok(self.variables[p].clone()),
}
}
pub fn does_variable_exist(&self, name: &str) -> bool {
self.variables.iter().any(|o| o.name() == name)
}
pub fn get_constraint(&self, name: &str) -> Result<Constraint, OError> {
match self.constraints.iter().position(|o| o.name() == name) {
None => Err(OError::NonExistingName(
"variable".to_string(),
name.to_string(),
)),
Some(p) => Ok(self.constraints[p].clone()),
}
}
pub fn objectives(&self) -> Vec<(String, Objective)> {
self.objectives
.iter()
.map(|o| (o.name().clone(), o.clone()))
.collect()
}
pub fn constraints(&self) -> Vec<(String, Constraint)> {
self.constraints
.iter()
.map(|o| (o.name().clone(), o.clone()))
.collect()
}
pub fn evaluator(&self) -> &dyn Evaluator {
self.evaluator.as_ref()
}
pub fn serialise(&self) -> ProblemExport {
let objectives: HashMap<String, Objective> = self
.objectives()
.iter()
.map(|(name, obj)| (name.clone(), obj.clone()))
.collect();
let constraints: HashMap<String, Constraint> = self
.constraints()
.iter()
.map(|(name, c)| (name.clone(), c.clone()))
.collect();
let variables: HashMap<String, VariableType> = self
.variables()
.iter()
.map(|(name, var)| (name.clone(), var.clone()))
.collect();
ProblemExport {
objectives,
constraints,
variables,
constraint_names: self.constraint_names().clone(),
variable_names: self.variable_names(),
objective_names: self.objective_names().clone(),
number_of_objectives: self.number_of_objectives(),
number_of_constraints: self.number_of_constraints(),
number_of_variables: self.number_of_variables(),
}
}
}
#[cfg(feature = "python")]
#[pyclass(name = "Problem", get_all)]
#[derive(Clone)]
pub struct PyProblem {
pub objectives_list: Vec<(String, Objective)>,
pub constraints_list: Vec<(String, Constraint)>,
pub variables_list: Vec<(String, VariableType)>,
pub constraint_names: Vec<String>,
pub variable_names: Vec<String>,
pub objective_names: Vec<String>,
pub number_of_objectives: usize,
pub number_of_constraints: usize,
pub number_of_variables: usize,
}
#[cfg(feature = "python")]
#[pymethods]
impl PyProblem {
#[getter]
pub fn variables(&self) -> PyResult<Py<PyDict>> {
Python::with_gil(|py| {
let dict = PyDict::new(py);
for (name, var) in &self.variables_list {
let var: PyVariable = var.into();
dict.set_item(name.clone(), var)?;
}
Ok(dict.unbind())
})
}
#[getter]
pub fn objectives(&self) -> PyResult<Py<PyDict>> {
Python::with_gil(|py| {
let dict = PyDict::new(py);
for (name, objective) in &self.objectives_list {
dict.set_item(name, objective.clone())?;
}
Ok(dict.unbind())
})
}
#[getter]
pub fn constraints(&self) -> PyResult<Py<PyDict>> {
Python::with_gil(|py| {
let dict = PyDict::new(py);
for (name, constraint) in &self.constraints_list {
dict.set_item(name, constraint.clone())?;
}
Ok(dict.unbind())
})
}
pub fn __repr__(&self) -> PyResult<String> {
Ok(format!(
"Problem(variables={}, objectives={}, constraints={})",
self.number_of_variables, self.number_of_objectives, self.number_of_constraints
))
}
pub fn __str__(&self) -> String {
self.__repr__().unwrap()
}
}
#[cfg(feature = "python")]
impl From<&Problem> for PyProblem {
fn from(p: &Problem) -> Self {
PyProblem {
variables_list: p.variables(),
objectives_list: p.objectives(),
constraints_list: p.constraints(),
constraint_names: p.constraint_names(),
variable_names: p.variable_names(),
objective_names: p.objective_names(),
number_of_objectives: p.number_of_objectives(),
number_of_constraints: p.number_of_constraints(),
number_of_variables: p.number_of_variables(),
}
}
}
pub mod builtin_problems {
use std::collections::HashMap;
use std::error::Error;
use std::f64::consts::PI;
use nalgebra::RealField;
use crate::core::{
BoundedNumber, Constraint, EvaluationResult, Evaluator, Individual, OError, Objective,
ObjectiveDirection, Problem, RelationalOperator, VariableType,
};
#[derive(Debug)]
pub struct SCHProblem;
impl SCHProblem {
pub fn create() -> Result<Problem, OError> {
let objectives = vec![
Objective::new("x^2", ObjectiveDirection::Minimise),
Objective::new("(x-2)^2", ObjectiveDirection::Minimise),
];
let variables = vec![VariableType::Real(BoundedNumber::new(
"x", -1000.0, 1000.0,
)?)];
let e = Box::new(SCHProblem);
Problem::new(objectives, variables, None, e)
}
pub fn f1(x: f64) -> f64 {
x.powi(2)
}
pub fn f2(x: f64) -> f64 {
(x - 2.0).powi(2)
}
}
impl Evaluator for SCHProblem {
fn evaluate(&self, i: &Individual) -> Result<EvaluationResult, Box<dyn Error>> {
let x = i.get_variable_value("x")?.as_real()?;
let mut objectives = HashMap::new();
objectives.insert("x^2".to_string(), SCHProblem::f1(x));
objectives.insert("(x-2)^2".to_string(), SCHProblem::f2(x));
Ok(EvaluationResult {
constraints: None,
objectives,
})
}
}
#[derive(Debug)]
pub struct FonProblem;
impl FonProblem {
pub fn create() -> Result<Problem, OError> {
let objectives = vec![
Objective::new("f1", ObjectiveDirection::Minimise),
Objective::new("f2", ObjectiveDirection::Minimise),
];
let variables = vec![
VariableType::Real(BoundedNumber::new("x1", -4.0, 4.0)?),
VariableType::Real(BoundedNumber::new("x2", -4.0, 4.0)?),
VariableType::Real(BoundedNumber::new("x3", -4.0, 4.0)?),
];
let e = Box::new(FonProblem);
Problem::new(objectives, variables, None, e)
}
}
impl Evaluator for FonProblem {
fn evaluate(&self, i: &Individual) -> Result<EvaluationResult, Box<dyn Error>> {
let mut x: Vec<f64> = Vec::new();
for var_name in ["x1", "x2", "x3"] {
x.push(i.get_variable_value(var_name)?.as_real()?);
}
let mut objectives = HashMap::new();
let mut exp_arg1 = 0.0;
let mut exp_arg2 = 0.0;
for x_val in x {
exp_arg1 += (x_val - 1.0 / 3.0_f64.sqrt()).powi(2);
exp_arg2 += (x_val + 1.0 / 3.0_f64.sqrt()).powi(2);
}
objectives.insert("f1".to_string(), 1.0 - f64::exp(-exp_arg1));
objectives.insert("f2".to_string(), 1.0 - f64::exp(-exp_arg2));
Ok(EvaluationResult {
constraints: None,
objectives,
})
}
}
#[derive(Debug)]
pub struct ZTD1Problem {
n: usize,
}
impl ZTD1Problem {
pub fn create(n: usize) -> Result<Problem, OError> {
let objectives = vec![
Objective::new("f1", ObjectiveDirection::Minimise),
Objective::new("f2", ObjectiveDirection::Minimise),
];
let mut variables: Vec<VariableType> = Vec::new();
for i in 1..=n {
variables.push(VariableType::Real(BoundedNumber::new(
format!("x{i}").as_str(),
0.0,
1.0,
)?));
}
let e = Box::new(ZTD1Problem { n });
Problem::new(objectives, variables, None, e)
}
pub fn f1(x: &[f64]) -> f64 {
x[0]
}
pub fn f2(&self, x: &[f64]) -> f64 {
let a: f64 = (1..self.n).map(|xi| x[xi]).sum();
let g = 1.0 + 9.0 * a / (self.n as f64 - 1.0);
g * (1.0 - f64::sqrt(x[0] / g))
}
}
impl Evaluator for ZTD1Problem {
fn evaluate(&self, i: &Individual) -> Result<EvaluationResult, Box<dyn Error>> {
let x: Vec<f64> = i
.get_variable_values()?
.iter()
.map(|v| v.as_real())
.collect::<Result<Vec<f64>, _>>()?;
let mut objectives = HashMap::new();
objectives.insert("f1".to_string(), ZTD1Problem::f1(&x));
objectives.insert("f2".to_string(), self.f2(&x));
Ok(EvaluationResult {
constraints: None,
objectives,
})
}
}
#[derive(Debug)]
pub struct ZTD2Problem {
n: usize,
}
impl ZTD2Problem {
pub fn create(n: usize) -> Result<Problem, OError> {
let objectives = vec![
Objective::new("f1", ObjectiveDirection::Minimise),
Objective::new("f2", ObjectiveDirection::Minimise),
];
let mut variables: Vec<VariableType> = Vec::new();
for i in 1..=n {
variables.push(VariableType::Real(BoundedNumber::new(
format!("x{i}").as_str(),
0.0,
1.0,
)?));
}
let e = Box::new(ZTD2Problem { n });
Problem::new(objectives, variables, None, e)
}
pub fn f1(x: &[f64]) -> f64 {
x[0]
}
pub fn f2(&self, x: &[f64]) -> f64 {
let a: f64 = (1..self.n).map(|xi| x[xi]).sum();
let g = 1.0 + 9.0 * a / (self.n as f64 - 1.0);
g * (1.0 - (x[0] / g).powi(2))
}
}
impl Evaluator for ZTD2Problem {
fn evaluate(&self, i: &Individual) -> Result<EvaluationResult, Box<dyn Error>> {
let x: Vec<f64> = i
.get_variable_values()?
.iter()
.map(|v| v.as_real())
.collect::<Result<Vec<f64>, _>>()?;
let mut objectives = HashMap::new();
objectives.insert("f1".to_string(), ZTD2Problem::f1(&x));
objectives.insert("f2".to_string(), self.f2(&x));
Ok(EvaluationResult {
constraints: None,
objectives,
})
}
}
#[derive(Debug)]
pub struct ZTD3Problem {
n: usize,
}
impl ZTD3Problem {
pub fn create(n: usize) -> Result<Problem, OError> {
let objectives = vec![
Objective::new("f1", ObjectiveDirection::Minimise),
Objective::new("f2", ObjectiveDirection::Minimise),
];
let mut variables: Vec<VariableType> = Vec::new();
for i in 1..=n {
variables.push(VariableType::Real(BoundedNumber::new(
format!("x{i}").as_str(),
0.0,
1.0,
)?));
}
let e = Box::new(ZTD3Problem { n });
Problem::new(objectives, variables, None, e)
}
pub fn f1(x: &[f64]) -> f64 {
x[0]
}
pub fn f2(&self, x: &[f64]) -> f64 {
let a: f64 = (1..self.n).map(|xi| x[xi]).sum();
let g = 1.0 + 9.0 * a / (self.n as f64 - 1.0);
g * (1.0 - (x[0] / g).powi(2) - x[0] / g * f64::sin(10.0 * PI * x[0]))
}
}
impl Evaluator for ZTD3Problem {
fn evaluate(&self, i: &Individual) -> Result<EvaluationResult, Box<dyn Error>> {
let x: Vec<f64> = i
.get_variable_values()?
.iter()
.map(|v| v.as_real())
.collect::<Result<Vec<f64>, _>>()?;
let mut objectives = HashMap::new();
objectives.insert("f1".to_string(), ZTD3Problem::f1(&x));
objectives.insert("f2".to_string(), self.f2(&x));
Ok(EvaluationResult {
constraints: None,
objectives,
})
}
}
#[derive(Debug)]
pub struct ZTD4Problem {
n: usize,
}
impl ZTD4Problem {
pub fn create(n: usize) -> Result<Problem, OError> {
let objectives = vec![
Objective::new("f1", ObjectiveDirection::Minimise),
Objective::new("f2", ObjectiveDirection::Minimise),
];
let mut variables: Vec<VariableType> = Vec::new();
variables.push(VariableType::Real(BoundedNumber::new("x1", 0.0, 1.0)?));
for i in 2..=n {
variables.push(VariableType::Real(BoundedNumber::new(
format!("x{i}").as_str(),
-5.0,
5.0,
)?));
}
let e = Box::new(ZTD4Problem { n });
Problem::new(objectives, variables, None, e)
}
pub fn f1(x: &[f64]) -> f64 {
x[0]
}
pub fn f2(&self, x: &[f64]) -> f64 {
let a: f64 = (1..self.n)
.map(|xi| {
let xi = x[xi];
xi.powi(2) - 10.0 * f64::cos(4.0 * PI * xi)
})
.sum();
let g: f64 = 1.0 + 10.0 * (self.n as f64 - 1.0) + a;
g * (1.0 - (x[0] / g).sqrt())
}
}
impl Evaluator for ZTD4Problem {
fn evaluate(&self, i: &Individual) -> Result<EvaluationResult, Box<dyn Error>> {
let x: Vec<f64> = i
.get_variable_values()?
.iter()
.map(|v| v.as_real())
.collect::<Result<Vec<f64>, _>>()?;
let mut objectives = HashMap::new();
objectives.insert("f1".to_string(), ZTD4Problem::f1(&x));
objectives.insert("f2".to_string(), self.f2(&x));
Ok(EvaluationResult {
constraints: None,
objectives,
})
}
}
#[derive(Debug)]
pub struct ZTD6Problem {
n: usize,
}
impl ZTD6Problem {
pub fn create(n: usize) -> Result<Problem, OError> {
let objectives = vec![
Objective::new("f1", ObjectiveDirection::Minimise),
Objective::new("f2", ObjectiveDirection::Minimise),
];
let mut variables: Vec<VariableType> = Vec::new();
for i in 1..=n {
variables.push(VariableType::Real(BoundedNumber::new(
format!("x{i}").as_str(),
0.0,
1.0,
)?));
}
let e = Box::new(ZTD6Problem { n });
Problem::new(objectives, variables, None, e)
}
pub fn f1(x: &[f64]) -> f64 {
1.0 - f64::exp(-4.0 * x[0]) * f64::powi(f64::sin(6.0 * PI * x[0]), 6)
}
pub fn f2(&self, x: &[f64]) -> f64 {
let a = (1..self.n).map(|xi| x[xi]).sum::<f64>() / (self.n as f64 - 1.0);
let g = 1.0 + 9.0 * f64::powf(a, 0.25);
g * (1.0 - (ZTD6Problem::f1(x) / g).powi(2))
}
}
impl Evaluator for ZTD6Problem {
fn evaluate(&self, i: &Individual) -> Result<EvaluationResult, Box<dyn Error>> {
let x: Vec<f64> = i
.get_variable_values()?
.iter()
.map(|v| v.as_real())
.collect::<Result<Vec<f64>, _>>()?;
let mut objectives = HashMap::new();
objectives.insert("f1".to_string(), ZTD6Problem::f1(&x));
objectives.insert("f2".to_string(), self.f2(&x));
Ok(EvaluationResult {
constraints: None,
objectives,
})
}
}
#[derive(Debug)]
pub struct DTLZ1Problem {
n_vars: usize,
n_objectives: usize,
invert: bool,
}
impl DTLZ1Problem {
pub fn create(n_vars: usize, n_objectives: usize, invert: bool) -> Result<Problem, OError> {
if n_vars + 1 < n_objectives {
return Err(OError::Generic(
"n_vars + 1 >= n_objectives not met. Increase n_vars.".to_string(),
));
}
let objectives = (1..=n_objectives)
.map(|i| Objective::new(format!("f{i}").as_str(), ObjectiveDirection::Minimise))
.collect();
let constraints: Vec<Constraint> = vec![Constraint::new(
"g",
RelationalOperator::GreaterOrEqualTo,
0.0,
)];
let mut variables: Vec<VariableType> = Vec::new();
for i in 1..=n_vars {
variables.push(VariableType::Real(BoundedNumber::new(
format!("x{i}").as_str(),
0.0,
1.0,
)?));
}
let e = Box::new(DTLZ1Problem {
n_vars,
n_objectives,
invert,
});
Problem::new(objectives, variables, Some(constraints), e)
}
}
impl Evaluator for DTLZ1Problem {
fn evaluate(&self, ind: &Individual) -> Result<EvaluationResult, Box<dyn Error>> {
let k = self.n_vars - self.n_objectives + 1;
let mut sum_g = Vec::new();
for i in (self.n_vars - k + 1)..=self.n_vars {
let xi = ind
.get_variable_value(format!("x{i}").as_str())?
.as_real()?;
sum_g.push((xi - 0.5).powi(2) - f64::cos(20.0 * f64::pi() * (xi - 0.5)));
}
let g = 100.0 * (k as f64 + sum_g.iter().sum::<f64>());
let mut constraints = HashMap::new();
constraints.insert("g".to_string(), g);
let mut objectives = HashMap::new();
for o in 1..=self.n_objectives {
let prod = if self.n_objectives == o {
1.0
} else {
let mut tmp = Vec::new();
for j in 1..=self.n_objectives - o {
tmp.push(
ind.get_variable_value(format!("x{j}").as_str())?
.as_real()?,
);
}
tmp.iter().product()
};
let delta = if o == 1 {
1.0
} else {
let x = ind
.get_variable_value(format!("x{}", self.n_objectives - o + 1).as_str())?
.as_real()?;
1.0 - x
};
let mut obj_value = 0.5 * prod * delta * (1.0 + g);
if self.invert {
obj_value = 0.5 * (1.0 + g) - obj_value;
}
objectives.insert(format!("f{o}"), obj_value);
}
Ok(EvaluationResult {
constraints: Some(constraints),
objectives,
})
}
}
#[derive(Debug)]
pub struct DTLZ2Problem {
n_vars: usize,
n_objectives: usize,
}
impl DTLZ2Problem {
pub fn create(n_vars: usize, n_objectives: usize) -> Result<Problem, OError> {
if n_vars + 1 < n_objectives {
return Err(OError::Generic(
"n_vars >= n_objectives not met. Increase n_vars.".to_string(),
));
}
let objectives = (1..=n_objectives)
.map(|i| Objective::new(format!("f{i}").as_str(), ObjectiveDirection::Minimise))
.collect();
let constraints: Vec<Constraint> = vec![Constraint::new(
"g",
RelationalOperator::GreaterOrEqualTo,
0.0,
)];
let mut variables: Vec<VariableType> = Vec::new();
for i in 1..=n_vars {
variables.push(VariableType::Real(BoundedNumber::new(
format!("x{i}").as_str(),
0.0,
1.0,
)?));
}
let e = Box::new(DTLZ2Problem {
n_vars,
n_objectives,
});
Problem::new(objectives, variables, Some(constraints), e)
}
}
impl Evaluator for DTLZ2Problem {
fn evaluate(&self, ind: &Individual) -> Result<EvaluationResult, Box<dyn Error>> {
let k = self.n_vars - self.n_objectives + 1;
let mut sum_g = Vec::new();
for i in (self.n_vars - k + 1)..=self.n_vars {
let xi = ind
.get_variable_value(format!("x{i}").as_str())?
.as_real()?;
sum_g.push((xi - 0.5).powi(2));
}
let g = sum_g.iter().sum::<f64>();
let mut constraints = HashMap::new();
constraints.insert("g".to_string(), g);
let mut objectives = HashMap::new();
let c = f64::pi() / 2.0;
for o in 1..=self.n_objectives {
let mut tmp = vec![];
for j in 1..=self.n_objectives - o {
tmp.push(f64::cos(
ind.get_variable_value(format!("x{j}").as_str())?
.as_real()?
* c,
));
}
if o > 1 {
let x = ind
.get_variable_value(format!("x{}", self.n_objectives - o + 1).as_str())?
.as_real()?;
tmp.push(f64::sin(x * c));
}
objectives.insert(format!("f{o}"), (1.0 + g) * tmp.iter().product::<f64>());
}
Ok(EvaluationResult {
constraints: Some(constraints),
objectives,
})
}
}
}
#[cfg(test)]
mod test {
use std::env;
use std::path::Path;
use std::sync::Arc;
use float_cmp::assert_approx_eq;
use crate::core::builtin_problems::{DTLZ1Problem, DTLZ2Problem};
use crate::core::test_utils::read_csv_test_file;
use crate::core::utils::dummy_evaluator;
use crate::core::{
BoundedNumber, Constraint, Individual, Objective, ObjectiveDirection, Problem,
RelationalOperator, VariableType, VariableValue,
};
#[test]
fn test_already_existing_data() {
let objectives = vec![
Objective::new("obj1", ObjectiveDirection::Minimise),
Objective::new("obj1", ObjectiveDirection::Maximise),
];
let var_types = vec![VariableType::Real(
BoundedNumber::new("X1", 0.0, 2.0).unwrap(),
)];
let var_types2 = var_types.clone();
let e = dummy_evaluator();
assert!(Problem::new(objectives, var_types, None, e).is_err());
let e = dummy_evaluator();
let objectives = vec![Objective::new("obj1", ObjectiveDirection::Minimise)];
let constraints = vec![
Constraint::new("c1", RelationalOperator::EqualTo, 1.0),
Constraint::new("c1", RelationalOperator::GreaterThan, -1.0),
];
assert!(Problem::new(objectives, var_types2, Some(constraints), e).is_err());
}
#[test]
fn test_dtlz1_optimal_solutions() {
let problem = Arc::new(DTLZ1Problem::create(4, 3, false).unwrap());
let mut individual = Individual::new(problem.clone());
individual
.update_variable("x1", VariableValue::Real(0.2))
.unwrap();
for i in 2..=problem.number_of_variables() {
individual
.update_variable(format!("x{i}").as_str(), VariableValue::Real(0.5))
.unwrap();
}
let data = problem.evaluator.evaluate(&individual).unwrap();
let constraints = data.constraints.clone().unwrap();
individual.update_constraint("g", constraints["g"]).unwrap();
assert!(
individual.is_feasible(),
"g must be larger or equal to 0 but was {:?}",
individual.get_constraint_value("g").unwrap()
);
assert_eq!(
problem
.objective_names()
.iter()
.map(|name| data.objectives[name])
.sum::<f64>(),
0.5
);
}
#[test]
fn test_dtlz1_random_solutions() {
let test_path = Path::new(&env::current_dir().unwrap())
.join("src")
.join("core")
.join("test_data");
let var_file = test_path.join("DTLZ1_variables.csv");
let obj_file = test_path.join("DTLZ1_objectives.csv");
let all_vars = read_csv_test_file(&var_file, None);
let all_expected_objectives = read_csv_test_file(&obj_file, None);
for (expected_objectives, vars) in all_expected_objectives.iter().zip(all_vars) {
let problem = Arc::new(DTLZ1Problem::create(vars.len(), 3, false).unwrap());
let mut individual = Individual::new(problem.clone());
for (i, var) in vars.iter().enumerate() {
individual
.update_variable(format!("x{}", i + 1).as_str(), VariableValue::Real(*var))
.unwrap();
}
let data = problem.evaluator.evaluate(&individual).unwrap();
for (i, obj) in expected_objectives.iter().enumerate() {
let name = format!("f{}", i + 1);
assert_approx_eq!(f64, *obj, data.objectives[&name], epsilon = 0.00001);
}
}
}
#[test]
fn test_dtlz2_optimal_solutions() {
let problem = Arc::new(DTLZ2Problem::create(4, 3).unwrap());
let mut individual = Individual::new(problem.clone());
individual
.update_variable("x1", VariableValue::Real(0.2))
.unwrap();
individual
.update_variable("x2", VariableValue::Real(0.2))
.unwrap();
for i in 3..=problem.number_of_variables() {
individual
.update_variable(format!("x{i}").as_str(), VariableValue::Real(0.5))
.unwrap();
}
let data = problem.evaluator.evaluate(&individual).unwrap();
let constraints = data.constraints.clone().unwrap();
individual.update_constraint("g", constraints["g"]).unwrap();
assert!(
individual.is_feasible(),
"g must be larger or equal to 0 but was {:?}",
individual.get_constraint_value("g").unwrap()
);
assert_approx_eq!(
f64,
problem
.objective_names()
.iter()
.map(|name| data.objectives[name].powi(2))
.sum::<f64>(),
1.0,
epsilon = 0.00001
);
}
}