use std::collections::HashMap;
use crate::dispatching::{RuleEngine, SchedulingContext};
use crate::models::{Assignment, Resource, Schedule, Task, TransitionMatrixCollection};
#[derive(Debug, Clone)]
pub struct ScheduleRequest {
pub tasks: Vec<Task>,
pub resources: Vec<Resource>,
pub start_time_ms: i64,
pub transition_matrices: TransitionMatrixCollection,
}
impl ScheduleRequest {
pub fn new(tasks: Vec<Task>, resources: Vec<Resource>) -> Self {
Self {
tasks,
resources,
start_time_ms: 0,
transition_matrices: TransitionMatrixCollection::new(),
}
}
pub fn with_start_time(mut self, start_time_ms: i64) -> Self {
self.start_time_ms = start_time_ms;
self
}
pub fn with_transition_matrices(mut self, matrices: TransitionMatrixCollection) -> Self {
self.transition_matrices = matrices;
self
}
}
#[derive(Debug, Clone)]
pub struct SimpleScheduler {
transition_matrices: TransitionMatrixCollection,
rule_engine: Option<RuleEngine>,
}
impl SimpleScheduler {
pub fn new() -> Self {
Self {
transition_matrices: TransitionMatrixCollection::new(),
rule_engine: None,
}
}
pub fn with_transition_matrices(mut self, matrices: TransitionMatrixCollection) -> Self {
self.transition_matrices = matrices;
self
}
pub fn with_rule_engine(mut self, engine: RuleEngine) -> Self {
self.rule_engine = Some(engine);
self
}
pub fn schedule(&self, tasks: &[Task], resources: &[Resource], start_time_ms: i64) -> Schedule {
let mut schedule = Schedule::new();
let mut resource_available: HashMap<String, i64> = HashMap::new();
let mut last_category: HashMap<String, String> = HashMap::new();
for resource in resources {
resource_available.insert(resource.id.clone(), start_time_ms);
}
let task_order = self.sort_tasks(tasks, start_time_ms);
for &task_idx in &task_order {
let task = &tasks[task_idx];
let mut task_start = task
.release_time
.unwrap_or(start_time_ms)
.max(start_time_ms);
for activity in &task.activities {
let candidates = activity.candidate_resources();
if candidates.is_empty() {
continue;
}
let mut best_resource: Option<&str> = None;
let mut best_start = i64::MAX;
for candidate in &candidates {
if let Some(&available) = resource_available.get(*candidate) {
let actual_start = available.max(task_start);
if actual_start < best_start {
best_start = actual_start;
best_resource = Some(candidate);
}
}
}
if let Some(resource_id) = best_resource {
let setup_time = if let Some(prev_cat) = last_category.get(resource_id) {
self.transition_matrices.get_transition_time(
resource_id,
prev_cat,
&task.category,
)
} else {
0
};
let start = best_start;
let end = start + setup_time + activity.duration.process_ms;
let assignment =
Assignment::new(&activity.id, &task.id, resource_id, start, end)
.with_setup(setup_time);
schedule.add_assignment(assignment);
resource_available.insert(resource_id.to_string(), end);
last_category.insert(resource_id.to_string(), task.category.clone());
task_start = end; }
}
}
schedule
}
pub fn schedule_request(&self, request: &ScheduleRequest) -> Schedule {
let scheduler = Self {
transition_matrices: request.transition_matrices.clone(),
rule_engine: self.rule_engine.clone(),
};
scheduler.schedule(&request.tasks, &request.resources, request.start_time_ms)
}
fn sort_tasks(&self, tasks: &[Task], start_time_ms: i64) -> Vec<usize> {
if let Some(ref engine) = self.rule_engine {
let ctx = SchedulingContext::at_time(start_time_ms);
engine.sort_indices(tasks, &ctx)
} else {
let mut indices: Vec<usize> = (0..tasks.len()).collect();
indices.sort_by(|&a, &b| tasks[b].priority.cmp(&tasks[a].priority));
indices
}
}
}
impl Default for SimpleScheduler {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::dispatching::rules;
use crate::models::{
Activity, ActivityDuration, Resource, ResourceRequirement, ResourceType, TransitionMatrix,
};
fn make_resource(id: &str) -> Resource {
Resource::new(id, ResourceType::Primary)
}
fn make_task_with_resource(
id: &str,
duration_ms: i64,
resource_id: &str,
priority: i32,
) -> Task {
Task::new(id)
.with_priority(priority)
.with_category("default")
.with_activity(
Activity::new(format!("{id}_O1"), id, 0)
.with_duration(ActivityDuration::fixed(duration_ms))
.with_requirement(
ResourceRequirement::new("Machine")
.with_candidates(vec![resource_id.into()]),
),
)
}
#[test]
fn test_simple_single_task() {
let tasks = vec![make_task_with_resource("J1", 1000, "M1", 0)];
let resources = vec![make_resource("M1")];
let scheduler = SimpleScheduler::new();
let schedule = scheduler.schedule(&tasks, &resources, 0);
assert_eq!(schedule.assignment_count(), 1);
let a = schedule.assignment_for_activity("J1_O1").unwrap();
assert_eq!(a.start_ms, 0);
assert_eq!(a.end_ms, 1000);
assert_eq!(a.resource_id, "M1");
}
#[test]
fn test_priority_ordering() {
let tasks = vec![
make_task_with_resource("low", 1000, "M1", 1),
make_task_with_resource("high", 1000, "M1", 10),
];
let resources = vec![make_resource("M1")];
let scheduler = SimpleScheduler::new();
let schedule = scheduler.schedule(&tasks, &resources, 0);
let high_a = schedule.assignment_for_activity("high_O1").unwrap();
let low_a = schedule.assignment_for_activity("low_O1").unwrap();
assert!(high_a.start_ms < low_a.start_ms);
}
#[test]
fn test_two_resources() {
let tasks = vec![
make_task_with_resource("J1", 2000, "M1", 10),
make_task_with_resource("J2", 1000, "M1", 5),
];
let resources = vec![make_resource("M1")];
let scheduler = SimpleScheduler::new();
let schedule = scheduler.schedule(&tasks, &resources, 0);
let j1 = schedule.assignment_for_activity("J1_O1").unwrap();
let j2 = schedule.assignment_for_activity("J2_O1").unwrap();
assert_eq!(j1.start_ms, 0);
assert_eq!(j1.end_ms, 2000);
assert_eq!(j2.start_ms, 2000);
assert_eq!(j2.end_ms, 3000);
}
#[test]
fn test_parallel_resources() {
let tasks = vec![
make_task_with_resource("J1", 2000, "M1", 10),
make_task_with_resource("J2", 1000, "M2", 5),
];
let resources = vec![make_resource("M1"), make_resource("M2")];
let scheduler = SimpleScheduler::new();
let schedule = scheduler.schedule(&tasks, &resources, 0);
let j1 = schedule.assignment_for_activity("J1_O1").unwrap();
let j2 = schedule.assignment_for_activity("J2_O1").unwrap();
assert_eq!(j1.start_ms, 0);
assert_eq!(j2.start_ms, 0);
}
#[test]
fn test_multi_activity_task() {
let task = Task::new("J1")
.with_priority(1)
.with_category("TypeA")
.with_activity(
Activity::new("O1", "J1", 0)
.with_duration(ActivityDuration::fixed(1000))
.with_requirement(
ResourceRequirement::new("Machine").with_candidates(vec!["M1".into()]),
),
)
.with_activity(
Activity::new("O2", "J1", 1)
.with_duration(ActivityDuration::fixed(2000))
.with_requirement(
ResourceRequirement::new("Machine").with_candidates(vec!["M1".into()]),
),
);
let resources = vec![make_resource("M1")];
let scheduler = SimpleScheduler::new();
let schedule = scheduler.schedule(&[task], &resources, 0);
let o1 = schedule.assignment_for_activity("O1").unwrap();
let o2 = schedule.assignment_for_activity("O2").unwrap();
assert_eq!(o1.end_ms, 1000);
assert!(o2.start_ms >= o1.end_ms);
assert_eq!(o2.end_ms, 3000);
}
#[test]
fn test_transition_matrix_setup() {
let mut tm = TransitionMatrix::new("changeover", "M1").with_default(500);
tm.set_transition("TypeA", "TypeB", 1000);
let matrices = TransitionMatrixCollection::new().with_matrix(tm);
let tasks = vec![
Task::new("J1")
.with_priority(10)
.with_category("TypeA")
.with_activity(
Activity::new("O1", "J1", 0)
.with_duration(ActivityDuration::fixed(1000))
.with_requirement(
ResourceRequirement::new("Machine").with_candidates(vec!["M1".into()]),
),
),
Task::new("J2")
.with_priority(5)
.with_category("TypeB")
.with_activity(
Activity::new("O2", "J2", 0)
.with_duration(ActivityDuration::fixed(1000))
.with_requirement(
ResourceRequirement::new("Machine").with_candidates(vec!["M1".into()]),
),
),
];
let resources = vec![make_resource("M1")];
let scheduler = SimpleScheduler::new().with_transition_matrices(matrices);
let schedule = scheduler.schedule(&tasks, &resources, 0);
let o2 = schedule.assignment_for_activity("O2").unwrap();
assert_eq!(o2.start_ms, 1000);
assert_eq!(o2.setup_ms, 1000);
assert_eq!(o2.end_ms, 3000);
}
#[test]
fn test_with_rule_engine() {
let tasks = vec![
make_task_with_resource("long", 5000, "M1", 100), make_task_with_resource("short", 1000, "M1", 1), ];
let resources = vec![make_resource("M1")];
let engine = RuleEngine::new().with_rule(rules::Spt);
let scheduler = SimpleScheduler::new().with_rule_engine(engine);
let schedule = scheduler.schedule(&tasks, &resources, 0);
let short_a = schedule.assignment_for_activity("short_O1").unwrap();
let long_a = schedule.assignment_for_activity("long_O1").unwrap();
assert_eq!(short_a.start_ms, 0);
assert!(long_a.start_ms >= short_a.end_ms);
}
#[test]
fn test_schedule_request() {
let tasks = vec![make_task_with_resource("J1", 1000, "M1", 0)];
let resources = vec![make_resource("M1")];
let request = ScheduleRequest::new(tasks, resources).with_start_time(5000);
let scheduler = SimpleScheduler::new();
let schedule = scheduler.schedule_request(&request);
let a = schedule.assignment_for_activity("J1_O1").unwrap();
assert_eq!(a.start_ms, 5000);
assert_eq!(a.end_ms, 6000);
}
#[test]
fn test_release_time_respected() {
let mut task = make_task_with_resource("J1", 1000, "M1", 0);
task.release_time = Some(5000);
let resources = vec![make_resource("M1")];
let scheduler = SimpleScheduler::new();
let schedule = scheduler.schedule(&[task], &resources, 0);
let a = schedule.assignment_for_activity("J1_O1").unwrap();
assert_eq!(a.start_ms, 5000);
}
#[test]
fn test_empty_input() {
let scheduler = SimpleScheduler::new();
let schedule = scheduler.schedule(&[], &[], 0);
assert_eq!(schedule.assignment_count(), 0);
assert_eq!(schedule.makespan_ms(), 0);
}
#[test]
fn test_no_candidate_resources() {
let task = Task::new("J1").with_priority(1).with_activity(
Activity::new("O1", "J1", 0).with_duration(ActivityDuration::fixed(1000)),
);
let resources = vec![make_resource("M1")];
let scheduler = SimpleScheduler::new();
let schedule = scheduler.schedule(&[task], &resources, 0);
assert_eq!(schedule.assignment_count(), 0);
}
}