1use serde::{Deserialize, Serialize};
11use std::collections::HashMap;
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
19pub enum Constraint {
20 Precedence {
25 before: String,
26 after: String,
27 min_delay_ms: i64,
28 },
29
30 Capacity {
32 resource_id: String,
33 max_capacity: i32,
34 },
35
36 TimeWindow {
38 activity_id: String,
39 start_ms: i64,
40 end_ms: i64,
41 },
42
43 NoOverlap {
46 resource_id: String,
47 activity_ids: Vec<String>,
48 },
49
50 TransitionCost {
54 from_category: String,
55 to_category: String,
56 cost_ms: i64,
57 },
58
59 Synchronize { activity_ids: Vec<String> },
61}
62
63impl Constraint {
64 pub fn precedence(before: impl Into<String>, after: impl Into<String>) -> Self {
66 Self::Precedence {
67 before: before.into(),
68 after: after.into(),
69 min_delay_ms: 0,
70 }
71 }
72
73 pub fn precedence_with_delay(
75 before: impl Into<String>,
76 after: impl Into<String>,
77 delay_ms: i64,
78 ) -> Self {
79 Self::Precedence {
80 before: before.into(),
81 after: after.into(),
82 min_delay_ms: delay_ms,
83 }
84 }
85
86 pub fn capacity(resource_id: impl Into<String>, max: i32) -> Self {
88 Self::Capacity {
89 resource_id: resource_id.into(),
90 max_capacity: max,
91 }
92 }
93
94 pub fn time_window(activity_id: impl Into<String>, start_ms: i64, end_ms: i64) -> Self {
96 Self::TimeWindow {
97 activity_id: activity_id.into(),
98 start_ms,
99 end_ms,
100 }
101 }
102
103 pub fn no_overlap(resource_id: impl Into<String>, activity_ids: Vec<String>) -> Self {
105 Self::NoOverlap {
106 resource_id: resource_id.into(),
107 activity_ids,
108 }
109 }
110
111 pub fn synchronize(activity_ids: Vec<String>) -> Self {
113 Self::Synchronize { activity_ids }
114 }
115}
116
117#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct TransitionMatrix {
128 pub name: String,
130 pub resource_id: String,
132 transitions: HashMap<(String, String), i64>,
134 pub default_ms: i64,
136}
137
138impl TransitionMatrix {
139 pub fn new(name: impl Into<String>, resource_id: impl Into<String>) -> Self {
141 Self {
142 name: name.into(),
143 resource_id: resource_id.into(),
144 transitions: HashMap::new(),
145 default_ms: 0,
146 }
147 }
148
149 pub fn with_default(mut self, default_ms: i64) -> Self {
151 self.default_ms = default_ms;
152 self
153 }
154
155 pub fn set_transition(&mut self, from: impl Into<String>, to: impl Into<String>, time_ms: i64) {
157 self.transitions.insert((from.into(), to.into()), time_ms);
158 }
159
160 pub fn get_transition(&self, from: &str, to: &str) -> i64 {
165 if from == to {
166 return *self
167 .transitions
168 .get(&(from.to_string(), to.to_string()))
169 .unwrap_or(&0);
170 }
171 *self
172 .transitions
173 .get(&(from.to_string(), to.to_string()))
174 .unwrap_or(&self.default_ms)
175 }
176
177 pub fn transition_count(&self) -> usize {
179 self.transitions.len()
180 }
181}
182
183#[derive(Debug, Clone, Default, Serialize, Deserialize)]
188pub struct TransitionMatrixCollection {
189 matrices: HashMap<String, TransitionMatrix>,
190}
191
192impl TransitionMatrixCollection {
193 pub fn new() -> Self {
195 Self::default()
196 }
197
198 pub fn add(&mut self, matrix: TransitionMatrix) {
200 self.matrices.insert(matrix.resource_id.clone(), matrix);
201 }
202
203 pub fn with_matrix(mut self, matrix: TransitionMatrix) -> Self {
205 self.add(matrix);
206 self
207 }
208
209 pub fn get_transition_time(&self, resource_id: &str, from: &str, to: &str) -> i64 {
213 self.matrices
214 .get(resource_id)
215 .map(|m| m.get_transition(from, to))
216 .unwrap_or(0)
217 }
218
219 pub fn len(&self) -> usize {
221 self.matrices.len()
222 }
223
224 pub fn is_empty(&self) -> bool {
226 self.matrices.is_empty()
227 }
228}
229
230#[cfg(test)]
231mod tests {
232 use super::*;
233
234 #[test]
235 fn test_precedence_constraint() {
236 let c = Constraint::precedence("O1", "O2");
237 match c {
238 Constraint::Precedence {
239 before,
240 after,
241 min_delay_ms,
242 } => {
243 assert_eq!(before, "O1");
244 assert_eq!(after, "O2");
245 assert_eq!(min_delay_ms, 0);
246 }
247 _ => panic!("wrong variant"),
248 }
249 }
250
251 #[test]
252 fn test_precedence_with_delay() {
253 let c = Constraint::precedence_with_delay("O1", "O2", 5000);
254 match c {
255 Constraint::Precedence { min_delay_ms, .. } => {
256 assert_eq!(min_delay_ms, 5000);
257 }
258 _ => panic!("wrong variant"),
259 }
260 }
261
262 #[test]
263 fn test_capacity_constraint() {
264 let c = Constraint::capacity("M1", 2);
265 match c {
266 Constraint::Capacity {
267 resource_id,
268 max_capacity,
269 } => {
270 assert_eq!(resource_id, "M1");
271 assert_eq!(max_capacity, 2);
272 }
273 _ => panic!("wrong variant"),
274 }
275 }
276
277 #[test]
278 fn test_transition_matrix() {
279 let mut tm = TransitionMatrix::new("changeover", "M1").with_default(500);
280
281 tm.set_transition("TypeA", "TypeB", 1000);
282 tm.set_transition("TypeB", "TypeA", 800);
283 tm.set_transition("TypeA", "TypeA", 100); assert_eq!(tm.get_transition("TypeA", "TypeB"), 1000);
286 assert_eq!(tm.get_transition("TypeB", "TypeA"), 800);
287 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);
291 }
292
293 #[test]
294 fn test_transition_matrix_same_category_default() {
295 let tm = TransitionMatrix::new("tm", "M1").with_default(200);
296 assert_eq!(tm.get_transition("X", "X"), 0);
298 assert_eq!(tm.get_transition("X", "Y"), 200);
300 }
301
302 #[test]
303 fn test_no_overlap_constraint() {
304 let c = Constraint::no_overlap("M1", vec!["O1".into(), "O2".into(), "O3".into()]);
305 match c {
306 Constraint::NoOverlap {
307 resource_id,
308 activity_ids,
309 } => {
310 assert_eq!(resource_id, "M1");
311 assert_eq!(activity_ids.len(), 3);
312 }
313 _ => panic!("wrong variant"),
314 }
315 }
316
317 #[test]
318 fn test_synchronize_constraint() {
319 let c = Constraint::synchronize(vec!["O1".into(), "O2".into()]);
320 match c {
321 Constraint::Synchronize { activity_ids } => {
322 assert_eq!(activity_ids.len(), 2);
323 }
324 _ => panic!("wrong variant"),
325 }
326 }
327}