mop-facades 0.0.6

Facades for MOP
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,
{
    /// # Panics
    ///
    /// * If `meta_heuirstics` is empty.
    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
    }
}