mop-facades 0.0.10

Facades for MOP
Documentation
use crate::{
  opt::{OptFacadeBuilder, OptHooks, OptProblem, OptProblemEvaluators},
  prelude::{BorrowAsRef, Cstr, Obj, OptSolver},
  ObjDirection,
};
use core::{
  fmt::Debug,
  ops::{Div, Sub},
};
use mop_blocks::Pct;
use mop_common_deps::num_traits::{NumCast, One, Zero};

#[derive(Debug)]
pub struct OptFacade<C, O, OH, OR, S, SD> {
  current_iteration: usize,
  last_fitness: OR,
  max_iterations: usize,
  objs_goals: Vec<OR>,
  opt_hooks: OH,
  opt_problem: Option<OptProblem<C, O, OR, S, SD>>,
  stagnation_lower_bound: OR,
  stagnation_percentage: Pct,
  stagnation_threshold_counter: usize,
  stagnation_threshold: usize,
  stagnation_upper_bound: OR,
}

impl<C, O, OH, OR, S, SD> OptFacade<C, O, OH, OR, S, SD>
where
  OH: OptHooks<S>,
  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_hooks: OH,
    opt_problem: OptProblem<C, O, OR, S, SD>,
    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_hooks,
      opt_problem: Some(opt_problem),
      stagnation_lower_bound: OR::one() - sp,
      stagnation_percentage,
      stagnation_threshold_counter: 0,
      stagnation_threshold,
      stagnation_upper_bound: OR::one() + sp,
    }
  }

  pub fn into_builder<I>(self) -> OptFacadeBuilder<C, I, O, OH, OR, S, SD> {
    OptFacadeBuilder {
      initial_solutions: None,
      max_iterations: Some(self.max_iterations),
      objs_goals: self.objs_goals,
      opt_hooks: Some(self.opt_hooks),
      opt_problem: self.opt_problem,
      stagnation_percentage: Some(self.stagnation_percentage),
      stagnation_threshold: Some(self.stagnation_threshold),
    }
  }

  pub fn opt_problem(self) -> OptProblem<C, O, OR, S, SD> {
    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: OptSolver<C, O, OR, S, SD>,
  {
    solver.set_opt_problem(self.opt_problem.take().unwrap());
    solver.init();
    self.opt_hooks.init();
    loop {
      solver
        .result_mut()
        .results_mut()
        .iter_mut()
        .for_each(|mut x| self.opt_hooks.before_iter(x.solution_mut()));
      solver.before_iter();
      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.after_iter();
      solver
        .result_mut()
        .results_mut()
        .iter_mut()
        .for_each(|mut x| self.opt_hooks.before_iter(*x.solution_mut()));
    }
    solver.finished();
    self.opt_hooks.finished();
    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, O, OH, OR, S, SD> OptFacade<C, O, OH, OR, S, SD>
where
  C: BorrowAsRef<dyn Cstr<S> + 'a>,
  O: BorrowAsRef<dyn Obj<OR, S> + 'a>,
  OH: OptHooks<S>,
  OR: Copy + Debug + Div<OR, Output = OR> + NumCast + Zero + Send + Sync,
  S: Copy + Send + Sync,
  SD: 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
  }
}