mop-facades 0.1.0

Facades for MOP
Documentation
use crate::{
  initial_solutions::InitialSolutions,
  opt::{OptFacadeBuilder, OptHooks},
};
use alloc::{string::String, vec::Vec};
use core::{
  fmt::Debug,
  marker::PhantomData,
  ops::{Div, Sub},
};
use mop_blocks::{
  dr_matrix::DrMatrixVec,
  mph::{Mph, MphOrsEvaluators},
  Cstr, Obj, ObjDirection, Pct, SolutionDomain,
};
use mop_common_defs::{Solver, TraitCfg};
use 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,
  stagnation_lower_bound: OR,
  stagnation_percentage: Pct,
  stagnation_threshold_counter: usize,
  stagnation_threshold: usize,
  phantom: PhantomData<(C, O, S, SD)>,
  stagnation_upper_bound: OR,
}

impl<C, O, OH, OR, S, SD> OptFacade<C, O, OH, OR, S, SD>
where
  OR: Copy + NumCast + One + Sub<OR, Output = OR> + Zero,
{
  pub fn new(
    max_iterations: usize,
    objs_goals: Vec<OR>,
    opt_hooks: OH,
    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,
      phantom: PhantomData,
      opt_hooks,
      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<OH, OR> {
    OptFacadeBuilder {
      max_iterations: Some(self.max_iterations),
      objs_goals: self.objs_goals,
      opt_hooks: Some(self.opt_hooks),
      stagnation_percentage: Some(self.stagnation_percentage),
      stagnation_threshold: Some(self.stagnation_threshold),
    }
  }
}

impl<C, O, OH, OR, S, SD> OptFacade<C, O, OH, OR, S, SD>
where
  C: Cstr<S>,
  O: Obj<OR, S>,
  OH: OptHooks<S>,
  OR: Copy + Div<OR, Output = OR> + NumCast + One + PartialOrd + Sub<OR, Output = OR> + Zero,
  SD: SolutionDomain<S>,
{
  /// Fill all solutions with initial data
  pub fn initial_solutions<I>(
    self,
    mut initial_solutions: I,
    problem: &mut Mph<C, O, OR, S, SD>,
  ) -> Self
  where
    I: InitialSolutions<C, O, OR, S, SD>,
  {
    problem.results_mut().clear();
    initial_solutions.initial_solutions(problem);
    self
  }

  pub async fn solve_problem_with<SOLVER>(
    mut self,
    problem: &mut Mph<C, O, OR, S, SD>,
    mut solver: SOLVER,
  ) -> Self
  where
    SOLVER: Solver<Problem = Mph<C, O, OR, S, SD>>,
  {
    solver.init(problem);
    self.opt_hooks.init();
    loop {
      {
        problem
          .results_mut()
          .iter_mut()
          .for_each(|mut x| self.opt_hooks.before_iter(x.solution_mut()));
      }
      solver.before_iter(problem).await;
      if self.iterations_number_has_extrapolated()
        || self.objs_are_not_converging(problem)
        || self.were_all_specified_goals_achieved(problem)
      {
        break;
      }
      solver.after_iter(problem).await;
      {
        problem
          .results_mut()
          .iter_mut()
          .for_each(|mut x| self.opt_hooks.after_iter(*x.solution_mut()));
      }
    }
    solver.finished(problem);
    self.opt_hooks.finished();
    self
  }

  fn objs_are_not_converging(&mut self, problem: &Mph<C, O, OR, S, SD>) -> bool {
    let fitness = **problem.results().best().unwrap().objs_avg();
    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 {
    self.current_iteration += 1;
    self.current_iteration >= self.max_iterations
  }

  fn were_all_specified_goals_achieved(&self, problem: &Mph<C, O, OR, S, SD>) -> bool
  where
    O: Obj<OR, S>,
  {
    let (defs, results) = problem.parts();
    let best = results.best().unwrap();
    let curr_objs = best.objs();
    let objs_defs = defs.objs();
    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.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()
  }
}

impl<C, O, OH, OR, S, SD> OptFacade<C, O, OH, OR, S, SD>
where
  C: Cstr<S> + TraitCfg,
  O: Obj<OR, S> + TraitCfg,
  SD: SolutionDomain<S>,
  OH: OptHooks<S>,
  OR: Copy + Debug + Div<OR, Output = OR> + NumCast + TraitCfg + Zero,
  S: Clone,
{
  pub async fn cstrs_reasons(&mut self, problem: &Mph<C, O, OR, S, SD>) -> DrMatrixVec<String> {
    let (defs, results) = problem.parts();
    MphOrsEvaluators::eval_cstrs_reasons(defs, results).await
  }
}