1use std::collections::HashMap;
17
18use crate::dispatching::{RuleEngine, SchedulingContext};
19use crate::models::{Assignment, Resource, Schedule, Task, TransitionMatrixCollection};
20
21#[derive(Debug, Clone)]
23pub struct ScheduleRequest {
24 pub tasks: Vec<Task>,
26 pub resources: Vec<Resource>,
28 pub start_time_ms: i64,
30 pub transition_matrices: TransitionMatrixCollection,
32}
33
34impl ScheduleRequest {
35 pub fn new(tasks: Vec<Task>, resources: Vec<Resource>) -> Self {
37 Self {
38 tasks,
39 resources,
40 start_time_ms: 0,
41 transition_matrices: TransitionMatrixCollection::new(),
42 }
43 }
44
45 pub fn with_start_time(mut self, start_time_ms: i64) -> Self {
47 self.start_time_ms = start_time_ms;
48 self
49 }
50
51 pub fn with_transition_matrices(mut self, matrices: TransitionMatrixCollection) -> Self {
53 self.transition_matrices = matrices;
54 self
55 }
56}
57
58#[derive(Debug, Clone)]
88pub struct SimpleScheduler {
89 transition_matrices: TransitionMatrixCollection,
90 rule_engine: Option<RuleEngine>,
91}
92
93impl SimpleScheduler {
94 pub fn new() -> Self {
96 Self {
97 transition_matrices: TransitionMatrixCollection::new(),
98 rule_engine: None,
99 }
100 }
101
102 pub fn with_transition_matrices(mut self, matrices: TransitionMatrixCollection) -> Self {
104 self.transition_matrices = matrices;
105 self
106 }
107
108 pub fn with_rule_engine(mut self, engine: RuleEngine) -> Self {
112 self.rule_engine = Some(engine);
113 self
114 }
115
116 pub fn schedule(&self, tasks: &[Task], resources: &[Resource], start_time_ms: i64) -> Schedule {
124 let mut schedule = Schedule::new();
125 let mut resource_available: HashMap<String, i64> = HashMap::new();
126 let mut last_category: HashMap<String, String> = HashMap::new();
127
128 for resource in resources {
130 resource_available.insert(resource.id.clone(), start_time_ms);
131 }
132
133 let task_order = self.sort_tasks(tasks, start_time_ms);
135
136 for &task_idx in &task_order {
138 let task = &tasks[task_idx];
139 let mut task_start = task
140 .release_time
141 .unwrap_or(start_time_ms)
142 .max(start_time_ms);
143
144 for activity in &task.activities {
145 let candidates = activity.candidate_resources();
146 if candidates.is_empty() {
147 continue;
148 }
149
150 let mut best_resource: Option<&str> = None;
152 let mut best_start = i64::MAX;
153
154 for candidate in &candidates {
155 if let Some(&available) = resource_available.get(*candidate) {
156 let actual_start = available.max(task_start);
157 if actual_start < best_start {
158 best_start = actual_start;
159 best_resource = Some(candidate);
160 }
161 }
162 }
163
164 if let Some(resource_id) = best_resource {
165 let setup_time = if let Some(prev_cat) = last_category.get(resource_id) {
167 self.transition_matrices.get_transition_time(
168 resource_id,
169 prev_cat,
170 &task.category,
171 )
172 } else {
173 0
174 };
175
176 let start = best_start;
177 let end = start + setup_time + activity.duration.process_ms;
178
179 let assignment =
180 Assignment::new(&activity.id, &task.id, resource_id, start, end)
181 .with_setup(setup_time);
182
183 schedule.add_assignment(assignment);
184
185 resource_available.insert(resource_id.to_string(), end);
187 last_category.insert(resource_id.to_string(), task.category.clone());
188 task_start = end; }
190 }
191 }
192
193 schedule
194 }
195
196 pub fn schedule_request(&self, request: &ScheduleRequest) -> Schedule {
198 let scheduler = Self {
199 transition_matrices: request.transition_matrices.clone(),
200 rule_engine: self.rule_engine.clone(),
201 };
202 scheduler.schedule(&request.tasks, &request.resources, request.start_time_ms)
203 }
204
205 fn sort_tasks(&self, tasks: &[Task], start_time_ms: i64) -> Vec<usize> {
207 if let Some(ref engine) = self.rule_engine {
208 let ctx = SchedulingContext::at_time(start_time_ms);
209 engine.sort_indices(tasks, &ctx)
210 } else {
211 let mut indices: Vec<usize> = (0..tasks.len()).collect();
213 indices.sort_by(|&a, &b| tasks[b].priority.cmp(&tasks[a].priority));
214 indices
215 }
216 }
217}
218
219impl Default for SimpleScheduler {
220 fn default() -> Self {
221 Self::new()
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228 use crate::dispatching::rules;
229 use crate::models::{
230 Activity, ActivityDuration, Resource, ResourceRequirement, ResourceType, TransitionMatrix,
231 };
232
233 fn make_resource(id: &str) -> Resource {
234 Resource::new(id, ResourceType::Primary)
235 }
236
237 fn make_task_with_resource(
238 id: &str,
239 duration_ms: i64,
240 resource_id: &str,
241 priority: i32,
242 ) -> Task {
243 Task::new(id)
244 .with_priority(priority)
245 .with_category("default")
246 .with_activity(
247 Activity::new(format!("{id}_O1"), id, 0)
248 .with_duration(ActivityDuration::fixed(duration_ms))
249 .with_requirement(
250 ResourceRequirement::new("Machine")
251 .with_candidates(vec![resource_id.into()]),
252 ),
253 )
254 }
255
256 #[test]
257 fn test_simple_single_task() {
258 let tasks = vec![make_task_with_resource("J1", 1000, "M1", 0)];
259 let resources = vec![make_resource("M1")];
260 let scheduler = SimpleScheduler::new();
261
262 let schedule = scheduler.schedule(&tasks, &resources, 0);
263 assert_eq!(schedule.assignment_count(), 1);
264
265 let a = schedule.assignment_for_activity("J1_O1").unwrap();
266 assert_eq!(a.start_ms, 0);
267 assert_eq!(a.end_ms, 1000);
268 assert_eq!(a.resource_id, "M1");
269 }
270
271 #[test]
272 fn test_priority_ordering() {
273 let tasks = vec![
274 make_task_with_resource("low", 1000, "M1", 1),
275 make_task_with_resource("high", 1000, "M1", 10),
276 ];
277 let resources = vec![make_resource("M1")];
278 let scheduler = SimpleScheduler::new();
279
280 let schedule = scheduler.schedule(&tasks, &resources, 0);
281
282 let high_a = schedule.assignment_for_activity("high_O1").unwrap();
284 let low_a = schedule.assignment_for_activity("low_O1").unwrap();
285 assert!(high_a.start_ms < low_a.start_ms);
286 }
287
288 #[test]
289 fn test_two_resources() {
290 let tasks = vec![
291 make_task_with_resource("J1", 2000, "M1", 10),
292 make_task_with_resource("J2", 1000, "M1", 5),
293 ];
294 let resources = vec![make_resource("M1")];
296 let scheduler = SimpleScheduler::new();
297
298 let schedule = scheduler.schedule(&tasks, &resources, 0);
299 let j1 = schedule.assignment_for_activity("J1_O1").unwrap();
300 let j2 = schedule.assignment_for_activity("J2_O1").unwrap();
301 assert_eq!(j1.start_ms, 0);
302 assert_eq!(j1.end_ms, 2000);
303 assert_eq!(j2.start_ms, 2000);
304 assert_eq!(j2.end_ms, 3000);
305 }
306
307 #[test]
308 fn test_parallel_resources() {
309 let tasks = vec![
311 make_task_with_resource("J1", 2000, "M1", 10),
312 make_task_with_resource("J2", 1000, "M2", 5),
313 ];
314 let resources = vec![make_resource("M1"), make_resource("M2")];
315 let scheduler = SimpleScheduler::new();
316
317 let schedule = scheduler.schedule(&tasks, &resources, 0);
318 let j1 = schedule.assignment_for_activity("J1_O1").unwrap();
319 let j2 = schedule.assignment_for_activity("J2_O1").unwrap();
320 assert_eq!(j1.start_ms, 0);
322 assert_eq!(j2.start_ms, 0);
323 }
324
325 #[test]
326 fn test_multi_activity_task() {
327 let task = Task::new("J1")
328 .with_priority(1)
329 .with_category("TypeA")
330 .with_activity(
331 Activity::new("O1", "J1", 0)
332 .with_duration(ActivityDuration::fixed(1000))
333 .with_requirement(
334 ResourceRequirement::new("Machine").with_candidates(vec!["M1".into()]),
335 ),
336 )
337 .with_activity(
338 Activity::new("O2", "J1", 1)
339 .with_duration(ActivityDuration::fixed(2000))
340 .with_requirement(
341 ResourceRequirement::new("Machine").with_candidates(vec!["M1".into()]),
342 ),
343 );
344
345 let resources = vec![make_resource("M1")];
346 let scheduler = SimpleScheduler::new();
347 let schedule = scheduler.schedule(&[task], &resources, 0);
348
349 let o1 = schedule.assignment_for_activity("O1").unwrap();
350 let o2 = schedule.assignment_for_activity("O2").unwrap();
351 assert_eq!(o1.end_ms, 1000);
353 assert!(o2.start_ms >= o1.end_ms);
354 assert_eq!(o2.end_ms, 3000);
355 }
356
357 #[test]
358 fn test_transition_matrix_setup() {
359 let mut tm = TransitionMatrix::new("changeover", "M1").with_default(500);
360 tm.set_transition("TypeA", "TypeB", 1000);
361
362 let matrices = TransitionMatrixCollection::new().with_matrix(tm);
363
364 let tasks = vec![
365 Task::new("J1")
366 .with_priority(10)
367 .with_category("TypeA")
368 .with_activity(
369 Activity::new("O1", "J1", 0)
370 .with_duration(ActivityDuration::fixed(1000))
371 .with_requirement(
372 ResourceRequirement::new("Machine").with_candidates(vec!["M1".into()]),
373 ),
374 ),
375 Task::new("J2")
376 .with_priority(5)
377 .with_category("TypeB")
378 .with_activity(
379 Activity::new("O2", "J2", 0)
380 .with_duration(ActivityDuration::fixed(1000))
381 .with_requirement(
382 ResourceRequirement::new("Machine").with_candidates(vec!["M1".into()]),
383 ),
384 ),
385 ];
386
387 let resources = vec![make_resource("M1")];
388 let scheduler = SimpleScheduler::new().with_transition_matrices(matrices);
389
390 let schedule = scheduler.schedule(&tasks, &resources, 0);
391 let o2 = schedule.assignment_for_activity("O2").unwrap();
392 assert_eq!(o2.start_ms, 1000);
394 assert_eq!(o2.setup_ms, 1000);
395 assert_eq!(o2.end_ms, 3000);
396 }
397
398 #[test]
399 fn test_with_rule_engine() {
400 let tasks = vec![
402 make_task_with_resource("long", 5000, "M1", 100), make_task_with_resource("short", 1000, "M1", 1), ];
405 let resources = vec![make_resource("M1")];
406 let engine = RuleEngine::new().with_rule(rules::Spt);
407 let scheduler = SimpleScheduler::new().with_rule_engine(engine);
408
409 let schedule = scheduler.schedule(&tasks, &resources, 0);
410 let short_a = schedule.assignment_for_activity("short_O1").unwrap();
411 let long_a = schedule.assignment_for_activity("long_O1").unwrap();
412 assert_eq!(short_a.start_ms, 0);
414 assert!(long_a.start_ms >= short_a.end_ms);
415 }
416
417 #[test]
418 fn test_schedule_request() {
419 let tasks = vec![make_task_with_resource("J1", 1000, "M1", 0)];
420 let resources = vec![make_resource("M1")];
421 let request = ScheduleRequest::new(tasks, resources).with_start_time(5000);
422
423 let scheduler = SimpleScheduler::new();
424 let schedule = scheduler.schedule_request(&request);
425
426 let a = schedule.assignment_for_activity("J1_O1").unwrap();
427 assert_eq!(a.start_ms, 5000);
428 assert_eq!(a.end_ms, 6000);
429 }
430
431 #[test]
432 fn test_release_time_respected() {
433 let mut task = make_task_with_resource("J1", 1000, "M1", 0);
434 task.release_time = Some(5000);
435 let resources = vec![make_resource("M1")];
436 let scheduler = SimpleScheduler::new();
437
438 let schedule = scheduler.schedule(&[task], &resources, 0);
439 let a = schedule.assignment_for_activity("J1_O1").unwrap();
440 assert_eq!(a.start_ms, 5000);
442 }
443
444 #[test]
445 fn test_empty_input() {
446 let scheduler = SimpleScheduler::new();
447 let schedule = scheduler.schedule(&[], &[], 0);
448 assert_eq!(schedule.assignment_count(), 0);
449 assert_eq!(schedule.makespan_ms(), 0);
450 }
451
452 #[test]
453 fn test_no_candidate_resources() {
454 let task = Task::new("J1").with_priority(1).with_activity(
456 Activity::new("O1", "J1", 0).with_duration(ActivityDuration::fixed(1000)),
457 );
459 let resources = vec![make_resource("M1")];
460 let scheduler = SimpleScheduler::new();
461 let schedule = scheduler.schedule(&[task], &resources, 0);
462 assert_eq!(schedule.assignment_count(), 0);
463 }
464}