use crate::{
opt_problem::{BorrowAsRef, Cstr, Obj, ObjDirection, OptProblem, OptProblemEvaluators},
prelude::OptFacadeSolver,
OptFacadeBuilder,
};
use core::{
fmt::Debug,
ops::{Div, Sub},
};
use mop_blocks::Pct;
use mop_common::num_traits::{NumCast, One, Zero};
#[derive(Debug)]
pub struct OptFacade<C, D, O, OR, S> {
current_iteration: usize,
last_fitness: OR,
max_iterations: usize,
objs_goals: Vec<OR>,
opt_problem: Option<OptProblem<C, D, O, OR, S>>,
stagnation_lower_bound: OR,
stagnation_percentage: Pct,
stagnation_threshold: usize,
stagnation_threshold_counter: usize,
stagnation_upper_bound: OR,
}
impl<C, D, O, OR, S> OptFacade<C, D, O, OR, S>
where
OR: Copy
+ Div<OR, Output = OR>
+ NumCast
+ One
+ PartialOrd
+ Sub<OR, Output = OR>
+ Zero
+ Send
+ Sync,
S: Copy,
{
pub fn new(
max_iterations: usize,
objs_goals: Vec<OR>,
opt_problem: OptProblem<C, D, O, OR, S>,
stagnation_percentage: Pct,
stagnation_threshold: usize,
) -> Self {
let sp = NumCast::from(*stagnation_percentage).unwrap();
OptFacade {
current_iteration: 0,
last_fitness: OR::zero(),
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, S> {
OptFacadeBuilder {
initial_solutions: None,
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, S> {
self.opt_problem.unwrap()
}
pub fn solve_with<'a, SOLVER>(mut self, mut solver: SOLVER) -> Self
where
O: BorrowAsRef<dyn Obj<OR, S> + 'a>,
SOLVER: OptFacadeSolver<C, D, O, OR, S>,
{
solver.set_opt_problem(self.opt_problem.take().unwrap());
solver.initialize();
loop {
solver.do_work_before();
let (defs, results) = solver.result().parts();
let best = results.best().unwrap();
if self.iterations_number_has_extrapolated()
|| self.objs_are_not_converging(**best.objs_avg())
|| self.were_all_specified_goals_achieved(defs.objs(), 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 were_all_specified_goals_achieved<'a>(&self, objs_defs: &[O], curr_objs: &[OR]) -> bool
where
O: BorrowAsRef<dyn Obj<OR, S> + 'a>,
{
let mut num_of_achieved_objs = 0;
for (obj_def, (curr_obj_val, specif_obj_goal)) in
objs_defs.iter().zip(curr_objs.iter().zip(&self.objs_goals))
{
match obj_def.borrow().obj_direction() {
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.is_empty() == false
}
}
impl<'a, C, D, O, OR, S> OptFacade<C, D, O, OR, S>
where
D: Send + Sync,
C: BorrowAsRef<dyn Cstr<S> + 'a>,
O: BorrowAsRef<dyn Obj<OR, S> + 'a>,
OR: Copy + Debug + Div<OR, Output = OR> + NumCast + Zero + Send + Sync,
S: Copy + 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
}
}