use crate::api::constraint_set::IncrementalConstraint;
use crate::constraint::complemented::ComplementedGroupConstraint;
use crate::stream::collector::count;
use solverforge_core::score::SoftScore;
use solverforge_core::{ConstraintRef, ImpactType};
#[derive(Clone, Hash, PartialEq, Eq)]
struct Employee {
id: usize,
}
#[derive(Clone)]
struct Shift {
employee_id: Option<usize>,
}
#[derive(Clone)]
struct Schedule {
employees: Vec<Employee>,
shifts: Vec<Shift>,
}
fn shifts(s: &Schedule) -> &[Shift] {
s.shifts.as_slice()
}
fn employees(s: &Schedule) -> &[Employee] {
s.employees.as_slice()
}
#[test]
fn test_complemented_evaluate() {
let constraint = ComplementedGroupConstraint::new(
ConstraintRef::new("", "Shift count"),
ImpactType::Penalty,
shifts,
employees,
|shift: &Shift| shift.employee_id,
|emp: &Employee| emp.id,
count::<Shift>(),
|_emp: &Employee| 0usize,
|count: &usize| SoftScore::of(*count as i64),
false,
);
let schedule = Schedule {
employees: vec![Employee { id: 0 }, Employee { id: 1 }],
shifts: vec![
Shift {
employee_id: Some(0),
},
Shift {
employee_id: Some(0),
},
],
};
assert_eq!(constraint.evaluate(&schedule), SoftScore::of(-2));
}
#[test]
fn test_complemented_skips_none_keys() {
let constraint = ComplementedGroupConstraint::new(
ConstraintRef::new("", "Shift count"),
ImpactType::Penalty,
shifts,
employees,
|shift: &Shift| shift.employee_id,
|emp: &Employee| emp.id,
count::<Shift>(),
|_emp: &Employee| 0usize,
|count: &usize| SoftScore::of(*count as i64),
false,
);
let schedule = Schedule {
employees: vec![Employee { id: 0 }, Employee { id: 1 }],
shifts: vec![
Shift {
employee_id: Some(0),
},
Shift {
employee_id: Some(0),
},
Shift { employee_id: None }, Shift { employee_id: None }, ],
};
assert_eq!(constraint.evaluate(&schedule), SoftScore::of(-2));
}
#[test]
fn test_complemented_incremental() {
let mut constraint = ComplementedGroupConstraint::new(
ConstraintRef::new("", "Shift count"),
ImpactType::Penalty,
shifts,
employees,
|shift: &Shift| shift.employee_id,
|emp: &Employee| emp.id,
count::<Shift>(),
|_emp: &Employee| 0usize,
|count: &usize| SoftScore::of(*count as i64),
false,
);
let schedule = Schedule {
employees: vec![Employee { id: 0 }, Employee { id: 1 }, Employee { id: 2 }],
shifts: vec![
Shift {
employee_id: Some(0),
},
Shift {
employee_id: Some(0),
},
Shift {
employee_id: Some(1),
},
],
};
let total = constraint.initialize(&schedule);
assert_eq!(total, SoftScore::of(-3));
let delta = constraint.on_retract(&schedule, 0, 0);
assert_eq!(delta, SoftScore::of(1));
let delta = constraint.on_insert(&schedule, 0, 0);
assert_eq!(delta, SoftScore::of(-1));
}
#[test]
fn test_complemented_incremental_with_none_keys() {
let mut constraint = ComplementedGroupConstraint::new(
ConstraintRef::new("", "Shift count"),
ImpactType::Penalty,
shifts,
employees,
|shift: &Shift| shift.employee_id,
|emp: &Employee| emp.id,
count::<Shift>(),
|_emp: &Employee| 0usize,
|count: &usize| SoftScore::of(*count as i64),
false,
);
let schedule = Schedule {
employees: vec![Employee { id: 0 }, Employee { id: 1 }],
shifts: vec![
Shift {
employee_id: Some(0),
},
Shift { employee_id: None }, Shift {
employee_id: Some(0),
},
],
};
let total = constraint.initialize(&schedule);
assert_eq!(total, SoftScore::of(-2));
let delta = constraint.on_retract(&schedule, 1, 0);
assert_eq!(delta, SoftScore::of(0));
let delta = constraint.on_insert(&schedule, 1, 0);
assert_eq!(delta, SoftScore::of(0));
}
#[test]
fn test_complemented_with_default() {
let constraint = ComplementedGroupConstraint::new(
ConstraintRef::new("", "Workload balance"),
ImpactType::Penalty,
shifts,
employees,
|shift: &Shift| shift.employee_id,
|emp: &Employee| emp.id,
count::<Shift>(),
|_emp: &Employee| 0usize,
|count: &usize| SoftScore::of((*count as i64).pow(2)),
false,
);
let schedule = Schedule {
employees: vec![Employee { id: 0 }, Employee { id: 1 }, Employee { id: 2 }],
shifts: vec![
Shift {
employee_id: Some(0),
},
Shift {
employee_id: Some(0),
},
Shift {
employee_id: Some(0),
},
],
};
assert_eq!(constraint.evaluate(&schedule), SoftScore::of(-9));
}
#[test]
fn test_complemented_incremental_matches_evaluate() {
let mut constraint = ComplementedGroupConstraint::new(
ConstraintRef::new("", "Shift count"),
ImpactType::Penalty,
shifts,
employees,
|shift: &Shift| shift.employee_id,
|emp: &Employee| emp.id,
count::<Shift>(),
|_emp: &Employee| 0usize,
|count: &usize| SoftScore::of((*count as i64).pow(2)),
false,
);
let schedule = Schedule {
employees: vec![Employee { id: 0 }, Employee { id: 1 }],
shifts: vec![
Shift {
employee_id: Some(0),
},
Shift {
employee_id: Some(0),
},
Shift {
employee_id: Some(1),
},
],
};
let init_total = constraint.initialize(&schedule);
let eval_total = constraint.evaluate(&schedule);
assert_eq!(init_total, eval_total);
assert_eq!(init_total, SoftScore::of(-5));
let mut running_total = init_total;
running_total = running_total + constraint.on_retract(&schedule, 2, 0);
assert_eq!(running_total, SoftScore::of(-4));
running_total = running_total + constraint.on_insert(&schedule, 2, 0);
assert_eq!(running_total, SoftScore::of(-5));
}