mop-blocks 0.5.0

Foundation blocks for MOP
Documentation
use crate::gp::{GpDefinitions, NoCstr, OneObj};
use alloc::vec::Vec;
use cl_traits::{Push, Storage};
use core::fmt;

pub type MpDefinitionsBuilder<D, OS> = GpDefinitionsBuilder<D, NoCstr, OS, NoCstr>;
pub type MphDefinitionsBuilder<D, HCS, OS> = GpDefinitionsBuilder<D, HCS, OS, NoCstr>;
pub type MphsDefinitionsBuilder<D, HCS, OS, SCS> = GpDefinitionsBuilder<D, HCS, OS, SCS>;
pub type SpDefinitionsBuilder<D, O> = GpDefinitionsBuilder<D, NoCstr, OneObj<O>, NoCstr>;

pub type MpDefinitionsBuilderVec<D, O> = MpDefinitionsBuilder<D, Vec<O>>;
pub type MphDefinitionsBuilderVec<D, HC, O> = MphDefinitionsBuilder<D, Vec<HC>, Vec<O>>;
pub type MphsDefinitionsBuilderVec<D, HC, O, SC> =
  MphsDefinitionsBuilder<D, Vec<HC>, Vec<O>, Vec<SC>>;
pub type SpDefinitionsBuilderVec<D, O> = SpDefinitionsBuilder<D, OneObj<O>>;

/// Definitions Builder for GP.
///
/// This structure is generic over single or multi objective problems, constrained or not.
///
/// # Types
///
/// * `D`: Domain
/// * `HCS`: Hard Constraints Storage
/// * `OR`: Objective Result
/// * `OS`: Objectives Storage
/// * `S`: Solution
/// * `SCS`: Soft Constraints Storage
#[derive(Clone, Debug)]
pub struct GpDefinitionsBuilder<D, HCS, OS, SCS> {
  pub(crate) domain: Option<D>,
  pub(crate) hard_cstrs: Option<HCS>,
  pub(crate) name: &'static str,
  pub(crate) objs: Option<OS>,
  pub(crate) soft_cstrs: Option<SCS>,
}

impl<D, HCS, OS, SCS> GpDefinitionsBuilder<D, HCS, OS, SCS> {
  pub fn build(self) -> Result<GpDefinitions<D, HCS, OS, SCS>, GpDefinitionsBuilderError>
  where
    HCS: Default,
    SCS: Default,
  {
    let domain = if let Some(r) = self.domain {
      r
    } else {
      return Err(GpDefinitionsBuilderError::NoDomainForDefinitionsBuilder);
    };
    let objs = if let Some(r) = self.objs {
      r
    } else {
      return Err(GpDefinitionsBuilderError::NoObjForDefinitionsBuilder);
    };
    Ok(GpDefinitions {
      domain,
      hard_cstrs: self.hard_cstrs.unwrap_or_default(),
      name: self.name,
      soft_cstrs: self.soft_cstrs.unwrap_or_default(),
      objs,
    })
  }

  pub fn domain(mut self, domain: D) -> Self {
    self.domain = Some(domain);
    self
  }

  pub fn name(mut self, name: &'static str) -> Self {
    self.name = name;
    self
  }
}

impl<D, HCS, OS, SCS> GpDefinitionsBuilder<D, HCS, OS, SCS> {
  /// Hard constraints
  pub fn hard_cstrs(mut self, hard_cstrs: HCS) -> Self {
    self.hard_cstrs = Some(hard_cstrs);
    self
  }
}

impl<D, HC, HCS, OS, SCS> GpDefinitionsBuilder<D, HCS, OS, SCS>
where
  HCS: Storage<Item = HC>,
{
  /// Push hard constraint
  pub fn push_hard_cstr(mut self, hard_cstr: HC) -> Self
  where
    HCS: Default + Push<Input = HC>,
  {
    if let Some(hcs) = &mut self.hard_cstrs {
      hcs.push(hard_cstr);
    } else {
      let mut hard_cstrs = HCS::default();
      hard_cstrs.push(hard_cstr);
      self.hard_cstrs = Some(hard_cstrs);
    }
    self
  }

  /// Push objectives
  pub fn push_hard_cstrs<CI>(self, hard_cstrs: CI) -> Self
  where
    CI: IntoIterator<Item = HC>,
    HCS: Default + Push<Input = HC>,
  {
    hard_cstrs.into_iter().fold(self, |this, c| this.push_hard_cstr(c))
  }
}

impl<D, HC, O, SC> GpDefinitionsBuilder<D, HC, [O; 1], SC> {
  /// Objective
  pub fn obj(mut self, obj: O) -> Self {
    self.objs = Some([obj]);
    self
  }
}

impl<D, HCS, O, OS, SCS> GpDefinitionsBuilder<D, HCS, OS, SCS>
where
  OS: Storage<Item = O>,
{
  /// Objectives
  pub fn objs(mut self, objs: OS) -> Self {
    self.objs = Some(objs);
    self
  }

  /// Push objective
  pub fn push_obj(mut self, obj: O) -> Self
  where
    OS: Default + Push<Input = O>,
  {
    if let Some(objs) = &mut self.objs {
      objs.push(obj);
    } else {
      let mut objs = OS::default();
      objs.push(obj);
      self.objs = Some(objs);
    }
    self
  }

  /// Push objectives
  pub fn push_objs<OI>(self, objs: OI) -> Self
  where
    OI: IntoIterator<Item = O>,
    OS: Default + Push<Input = O>,
  {
    objs.into_iter().fold(self, |this, o| this.push_obj(o))
  }
}

impl<D, HCS, OS, SCS> GpDefinitionsBuilder<D, HCS, OS, SCS> {
  /// Soft constraints
  pub fn soft_cstrs(mut self, soft_cstrs: SCS) -> Self {
    self.soft_cstrs = Some(soft_cstrs);
    self
  }
}

impl<D, HCS, OS, SC, SCS> GpDefinitionsBuilder<D, HCS, OS, SCS>
where
  SCS: Storage<Item = SC>,
{
  /// Push soft constraint
  pub fn push_soft_cstr(mut self, soft_cstr: SC) -> Self
  where
    SCS: Default + Push<Input = SC>,
  {
    if let Some(hcs) = &mut self.soft_cstrs {
      hcs.push(soft_cstr);
    } else {
      let mut soft_cstrs = SCS::default();
      soft_cstrs.push(soft_cstr);
      self.soft_cstrs = Some(soft_cstrs);
    }
    self
  }

  /// Push soft constraints
  pub fn push_soft_cstrs<CI>(self, soft_cstrs: CI) -> Self
  where
    CI: IntoIterator<Item = SC>,
    SCS: Default + Push<Input = SC>,
  {
    soft_cstrs.into_iter().fold(self, |this, c| this.push_soft_cstr(c))
  }
}

impl<D, HCS, OS, SCS> Default for GpDefinitionsBuilder<D, HCS, OS, SCS> {
  fn default() -> Self {
    Self { domain: None, hard_cstrs: None, name: "Unknown problem", objs: None, soft_cstrs: None }
  }
}

#[derive(Debug)]
pub enum GpDefinitionsBuilderError {
  /// A domain must be included
  NoDomainForDefinitionsBuilder,
  /// Must have at least one objective
  NoObjForDefinitionsBuilder,
}

#[cfg(feature = "std")]
impl std::error::Error for GpDefinitionsBuilderError {}

impl fmt::Display for GpDefinitionsBuilderError {
  fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
    let s = match self {
      Self::NoDomainForDefinitionsBuilder => "NoDomainForDefinitionsBuilder",
      Self::NoObjForDefinitionsBuilder => "NoObjForDefinitionsBuilder",
    };
    write!(f, "{}", s)
  }
}