#[cfg(test)]
#[path = "../../../tests/unit/construction/constraints/pipeline_test.rs"]
mod pipeline_test;
use crate::construction::heuristics::{ActivityContext, RouteContext, SolutionContext};
use crate::models::common::Cost;
use crate::models::problem::Job;
use hashbrown::HashSet;
use std::slice::Iter;
use std::sync::Arc;
pub trait HardRouteConstraint {
fn evaluate_job(
&self,
solution_ctx: &SolutionContext,
ctx: &RouteContext,
job: &Job,
) -> Option<RouteConstraintViolation>;
}
pub trait SoftRouteConstraint {
fn estimate_job(&self, solution_ctx: &SolutionContext, route_ctx: &RouteContext, job: &Job) -> Cost;
}
pub trait HardActivityConstraint {
fn evaluate_activity(
&self,
route_ctx: &RouteContext,
activity_ctx: &ActivityContext,
) -> Option<ActivityConstraintViolation>;
}
pub trait SoftActivityConstraint {
fn estimate_activity(&self, route_ctx: &RouteContext, activity_ctx: &ActivityContext) -> Cost;
}
#[derive(Clone, Debug)]
pub struct RouteConstraintViolation {
pub code: i32,
}
#[derive(Clone, Debug)]
pub struct ActivityConstraintViolation {
pub code: i32,
pub stopped: bool,
}
pub enum ConstraintVariant {
HardRoute(Arc<dyn HardRouteConstraint + Send + Sync>),
HardActivity(Arc<dyn HardActivityConstraint + Send + Sync>),
SoftRoute(Arc<dyn SoftRouteConstraint + Send + Sync>),
SoftActivity(Arc<dyn SoftActivityConstraint + Send + Sync>),
}
pub trait ConstraintModule {
fn accept_insertion(&self, solution_ctx: &mut SolutionContext, route_index: usize, job: &Job);
fn accept_route_state(&self, ctx: &mut RouteContext);
fn accept_solution_state(&self, ctx: &mut SolutionContext);
fn state_keys(&self) -> Iter<i32>;
fn get_constraints(&self) -> Iter<ConstraintVariant>;
}
pub struct ConstraintPipeline {
modules: Vec<Box<dyn ConstraintModule + Send + Sync>>,
state_keys: HashSet<i32>,
hard_route_constraints: Vec<Arc<dyn HardRouteConstraint + Send + Sync>>,
hard_activity_constraints: Vec<Arc<dyn HardActivityConstraint + Send + Sync>>,
soft_route_constraints: Vec<Arc<dyn SoftRouteConstraint + Send + Sync>>,
soft_activity_constraints: Vec<Arc<dyn SoftActivityConstraint + Send + Sync>>,
}
impl Default for ConstraintPipeline {
fn default() -> Self {
ConstraintPipeline {
modules: vec![],
state_keys: Default::default(),
hard_route_constraints: vec![],
hard_activity_constraints: vec![],
soft_route_constraints: vec![],
soft_activity_constraints: vec![],
}
}
}
impl ConstraintPipeline {
pub fn accept_insertion(&self, solution_ctx: &mut SolutionContext, route_index: usize, job: &Job) {
self.modules.iter().for_each(|c| c.accept_insertion(solution_ctx, route_index, job))
}
pub fn accept_route_state(&self, ctx: &mut RouteContext) {
self.modules.iter().for_each(|c| c.accept_route_state(ctx))
}
pub fn accept_solution_state(&self, ctx: &mut SolutionContext) {
self.modules.iter().for_each(|c| c.accept_solution_state(ctx))
}
pub fn add_module(&mut self, module: Box<dyn ConstraintModule + Send + Sync>) -> &mut Self {
module.state_keys().for_each(|key| {
if let Some(duplicate) = self.state_keys.get(key) {
panic!("Attempt to register constraint with key duplication: {}", duplicate)
}
self.state_keys.insert(*key);
});
module.get_constraints().for_each(|c| match c {
ConstraintVariant::HardRoute(c) => self.hard_route_constraints.push(c.clone()),
ConstraintVariant::HardActivity(c) => self.hard_activity_constraints.push(c.clone()),
ConstraintVariant::SoftRoute(c) => self.soft_route_constraints.push(c.clone()),
ConstraintVariant::SoftActivity(c) => self.soft_activity_constraints.push(c.clone()),
});
self.modules.push(module);
self
}
pub fn evaluate_hard_route(
&self,
solution_ctx: &SolutionContext,
route_ctx: &RouteContext,
job: &Job,
) -> Option<RouteConstraintViolation> {
self.hard_route_constraints.iter().find_map(|c| c.evaluate_job(solution_ctx, route_ctx, job))
}
pub fn evaluate_hard_activity(
&self,
route_ctx: &RouteContext,
activity_ctx: &ActivityContext,
) -> Option<ActivityConstraintViolation> {
self.hard_activity_constraints.iter().find_map(|c| c.evaluate_activity(route_ctx, activity_ctx))
}
pub fn evaluate_soft_route(&self, solution_ctx: &SolutionContext, route_ctx: &RouteContext, job: &Job) -> Cost {
self.soft_route_constraints.iter().map(|c| c.estimate_job(solution_ctx, route_ctx, job)).sum()
}
pub fn evaluate_soft_activity(&self, route_ctx: &RouteContext, activity_ctx: &ActivityContext) -> Cost {
self.soft_activity_constraints.iter().map(|c| c.estimate_activity(route_ctx, activity_ctx)).sum()
}
}
impl PartialEq<RouteConstraintViolation> for RouteConstraintViolation {
fn eq(&self, other: &RouteConstraintViolation) -> bool {
self.code == other.code
}
}
impl Eq for RouteConstraintViolation {}
impl PartialEq<ActivityConstraintViolation> for ActivityConstraintViolation {
fn eq(&self, other: &ActivityConstraintViolation) -> bool {
self.code == other.code && self.stopped == other.stopped
}
}
impl Eq for ActivityConstraintViolation {}