use core::{
fmt::Debug,
ops::{Div, Sub},
time::Duration,
};
use crate::{
opt_problem::{BorrowAsRef, Cstr, Domain, Obj, ObjDirection, OptProblem, OptProblemEvaluators},
prelude::OptFacadeSolver,
OptFacadeBuilder,
};
use mop_blocks::Pct;
use mop_common::num_traits::{NumCast, One, Zero};
use std::time::Instant;
#[derive(Debug)]
pub struct OptFacade<C, D, O, OR>
where
D: Domain,
{
current_duration: Option<Instant>,
current_iteration: usize,
last_fitness: OR,
max_duration: Duration,
max_iterations: usize,
objs_goals: Vec<(ObjDirection, OR)>,
opt_problem: Option<OptProblem<C, D, O, OR>>,
stagnation_lower_bound: OR,
stagnation_percentage: Pct,
stagnation_threshold: usize,
stagnation_threshold_counter: usize,
stagnation_upper_bound: OR,
}
impl<C, D, O, OR> OptFacade<C, D, O, OR>
where
D: Domain,
D::Solution: Copy + Debug,
OR: Copy
+ Div<OR, Output = OR>
+ NumCast
+ One
+ PartialOrd
+ Sub<OR, Output = OR>
+ Zero
+ Send
+ Sync,
{
pub fn new(
max_duration: Duration,
max_iterations: usize,
objs_goals: Vec<(ObjDirection, OR)>,
opt_problem: OptProblem<C, D, O, OR>,
stagnation_percentage: Pct,
stagnation_threshold: usize,
) -> Self {
let sp = NumCast::from(*stagnation_percentage).unwrap();
OptFacade {
current_duration: None,
current_iteration: 0,
last_fitness: OR::zero(),
max_duration,
max_iterations,
objs_goals,
opt_problem: Some(opt_problem),
stagnation_lower_bound: OR::one() - sp,
stagnation_percentage,
stagnation_threshold,
stagnation_threshold_counter: 0,
stagnation_upper_bound: OR::one() + sp,
}
}
pub fn into_builder<I>(self) -> OptFacadeBuilder<C, D, I, O, OR> {
OptFacadeBuilder {
initial_solutions: None,
max_duration: Some(self.max_duration),
max_iterations: Some(self.max_iterations),
objs_goals: self.objs_goals,
opt_problem: self.opt_problem,
stagnation_percentage: Some(self.stagnation_percentage),
stagnation_threshold: Some(self.stagnation_threshold),
}
}
pub fn opt_problem(self) -> OptProblem<C, D, O, OR> {
self.opt_problem.unwrap()
}
pub fn solve_with<'a, S>(mut self, mut solver: S) -> Self
where
S: OptFacadeSolver<C, D, O, OR>,
{
self.current_duration = Some(Instant::now());
solver.set_opt_problem(self.opt_problem.take().unwrap());
solver.initialize();
loop {
solver.do_work_before();
let best = solver.result().results().best().unwrap();
if self.time_has_expired()
|| self.iterations_number_has_extrapolated()
|| self.objs_are_not_converging(**best.objs_avg())
|| self.were_all_specified_goals_achieved(best.objs())
{
break;
}
solver.do_work_after();
}
solver.terminate();
self.opt_problem = Some(solver.into_result());
self
}
fn objs_are_not_converging(&mut self, fitness: OR) -> bool {
if fitness >= self.last_fitness * self.stagnation_lower_bound
&& fitness <= self.last_fitness * self.stagnation_upper_bound
{
self.stagnation_threshold_counter += 1;
} else {
self.stagnation_threshold_counter = 0;
}
self.last_fitness = fitness;
self.stagnation_threshold_counter >= self.stagnation_threshold
}
fn iterations_number_has_extrapolated(&mut self) -> bool {
let result = self.current_iteration >= self.max_iterations;
self.current_iteration += 1;
result
}
fn time_has_expired(&self) -> bool {
self.current_duration.unwrap().elapsed() > self.max_duration
}
fn were_all_specified_goals_achieved(&self, objs: &[OR]) -> bool {
let mut num_of_achieved_objs = 0;
for (curr_obj_val, (specif_dir, specif_obj_goal)) in objs.iter().zip(&self.objs_goals) {
match specif_dir {
ObjDirection::Max => {
if curr_obj_val >= specif_obj_goal {
num_of_achieved_objs += 1;
}
}
ObjDirection::Min => {
if curr_obj_val <= specif_obj_goal {
num_of_achieved_objs += 1;
}
}
}
}
num_of_achieved_objs == self.objs_goals.len() && self.objs_goals.len() > 0
}
}
impl<C, D, O, OR> OptFacade<C, D, O, OR>
where
C: BorrowAsRef<Cstr<D::Solution>>,
D: Domain,
D::Solution: Copy + Debug,
O: BorrowAsRef<Obj<OR, D::Solution>>,
OR: Copy + Debug + Div<OR, Output = OR> + NumCast + Zero + Send + Sync,
{
pub fn validate_cstrs(mut self) -> Self {
let (defs, results) = self.opt_problem.as_mut().unwrap().parts_mut();
OptProblemEvaluators::eval_reasons(defs, results);
self
}
}