use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Constraint {
Precedence {
before: String,
after: String,
min_delay_ms: i64,
},
Capacity {
resource_id: String,
max_capacity: i32,
},
TimeWindow {
activity_id: String,
start_ms: i64,
end_ms: i64,
},
NoOverlap {
resource_id: String,
activity_ids: Vec<String>,
},
TransitionCost {
from_category: String,
to_category: String,
cost_ms: i64,
},
Synchronize { activity_ids: Vec<String> },
}
impl Constraint {
pub fn precedence(before: impl Into<String>, after: impl Into<String>) -> Self {
Self::Precedence {
before: before.into(),
after: after.into(),
min_delay_ms: 0,
}
}
pub fn precedence_with_delay(
before: impl Into<String>,
after: impl Into<String>,
delay_ms: i64,
) -> Self {
Self::Precedence {
before: before.into(),
after: after.into(),
min_delay_ms: delay_ms,
}
}
pub fn capacity(resource_id: impl Into<String>, max: i32) -> Self {
Self::Capacity {
resource_id: resource_id.into(),
max_capacity: max,
}
}
pub fn time_window(activity_id: impl Into<String>, start_ms: i64, end_ms: i64) -> Self {
Self::TimeWindow {
activity_id: activity_id.into(),
start_ms,
end_ms,
}
}
pub fn no_overlap(resource_id: impl Into<String>, activity_ids: Vec<String>) -> Self {
Self::NoOverlap {
resource_id: resource_id.into(),
activity_ids,
}
}
pub fn synchronize(activity_ids: Vec<String>) -> Self {
Self::Synchronize { activity_ids }
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TransitionMatrix {
pub name: String,
pub resource_id: String,
transitions: HashMap<(String, String), i64>,
pub default_ms: i64,
}
impl TransitionMatrix {
pub fn new(name: impl Into<String>, resource_id: impl Into<String>) -> Self {
Self {
name: name.into(),
resource_id: resource_id.into(),
transitions: HashMap::new(),
default_ms: 0,
}
}
pub fn with_default(mut self, default_ms: i64) -> Self {
self.default_ms = default_ms;
self
}
pub fn set_transition(&mut self, from: impl Into<String>, to: impl Into<String>, time_ms: i64) {
self.transitions.insert((from.into(), to.into()), time_ms);
}
pub fn get_transition(&self, from: &str, to: &str) -> i64 {
if from == to {
return *self
.transitions
.get(&(from.to_string(), to.to_string()))
.unwrap_or(&0);
}
*self
.transitions
.get(&(from.to_string(), to.to_string()))
.unwrap_or(&self.default_ms)
}
pub fn transition_count(&self) -> usize {
self.transitions.len()
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TransitionMatrixCollection {
matrices: HashMap<String, TransitionMatrix>,
}
impl TransitionMatrixCollection {
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, matrix: TransitionMatrix) {
self.matrices.insert(matrix.resource_id.clone(), matrix);
}
pub fn with_matrix(mut self, matrix: TransitionMatrix) -> Self {
self.add(matrix);
self
}
pub fn get_transition_time(&self, resource_id: &str, from: &str, to: &str) -> i64 {
self.matrices
.get(resource_id)
.map(|m| m.get_transition(from, to))
.unwrap_or(0)
}
pub fn len(&self) -> usize {
self.matrices.len()
}
pub fn is_empty(&self) -> bool {
self.matrices.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_precedence_constraint() {
let c = Constraint::precedence("O1", "O2");
match c {
Constraint::Precedence {
before,
after,
min_delay_ms,
} => {
assert_eq!(before, "O1");
assert_eq!(after, "O2");
assert_eq!(min_delay_ms, 0);
}
_ => panic!("wrong variant"),
}
}
#[test]
fn test_precedence_with_delay() {
let c = Constraint::precedence_with_delay("O1", "O2", 5000);
match c {
Constraint::Precedence { min_delay_ms, .. } => {
assert_eq!(min_delay_ms, 5000);
}
_ => panic!("wrong variant"),
}
}
#[test]
fn test_capacity_constraint() {
let c = Constraint::capacity("M1", 2);
match c {
Constraint::Capacity {
resource_id,
max_capacity,
} => {
assert_eq!(resource_id, "M1");
assert_eq!(max_capacity, 2);
}
_ => panic!("wrong variant"),
}
}
#[test]
fn test_transition_matrix() {
let mut tm = TransitionMatrix::new("changeover", "M1").with_default(500);
tm.set_transition("TypeA", "TypeB", 1000);
tm.set_transition("TypeB", "TypeA", 800);
tm.set_transition("TypeA", "TypeA", 100);
assert_eq!(tm.get_transition("TypeA", "TypeB"), 1000);
assert_eq!(tm.get_transition("TypeB", "TypeA"), 800);
assert_eq!(tm.get_transition("TypeA", "TypeA"), 100); assert_eq!(tm.get_transition("TypeB", "TypeB"), 0); assert_eq!(tm.get_transition("TypeC", "TypeD"), 500); assert_eq!(tm.transition_count(), 3);
}
#[test]
fn test_transition_matrix_same_category_default() {
let tm = TransitionMatrix::new("tm", "M1").with_default(200);
assert_eq!(tm.get_transition("X", "X"), 0);
assert_eq!(tm.get_transition("X", "Y"), 200);
}
#[test]
fn test_no_overlap_constraint() {
let c = Constraint::no_overlap("M1", vec!["O1".into(), "O2".into(), "O3".into()]);
match c {
Constraint::NoOverlap {
resource_id,
activity_ids,
} => {
assert_eq!(resource_id, "M1");
assert_eq!(activity_ids.len(), 3);
}
_ => panic!("wrong variant"),
}
}
#[test]
fn test_synchronize_constraint() {
let c = Constraint::synchronize(vec!["O1".into(), "O2".into()]);
match c {
Constraint::Synchronize { activity_ids } => {
assert_eq!(activity_ids.len(), 2);
}
_ => panic!("wrong variant"),
}
}
}