use solverforge_core::score::SoftScore;
use crate::api::constraint_set::IncrementalConstraint;
use crate::stream::collection_extract::{source, ChangeSource};
use crate::stream::collector::count;
use crate::stream::joiner::{equal_bi, less_than, overlapping};
use crate::stream::ConstraintFactory;
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
struct Shift {
employee_id: Option<usize>,
start: i64,
end: i64,
}
#[derive(Clone, Hash, PartialEq, Eq, Debug)]
struct Employee {
id: usize,
}
#[derive(Clone)]
struct Schedule {
shifts: Vec<Shift>,
employees: Vec<Employee>,
}
fn shifts(s: &Schedule) -> &[Shift] {
s.shifts.as_slice()
}
fn employees(s: &Schedule) -> &[Employee] {
s.employees.as_slice()
}
fn complement_skips_none_keys() -> impl IncrementalConstraint<Schedule, SoftScore> {
ConstraintFactory::<Schedule, SoftScore>::new()
.for_each(source(
shifts as fn(&Schedule) -> &[Shift],
ChangeSource::Descriptor(0),
))
.group_by(|s: &Shift| s.employee_id.unwrap_or(usize::MAX), count())
.complement_with_key(
source(
employees as fn(&Schedule) -> &[Employee],
ChangeSource::Descriptor(1),
),
|s: &Shift| s.employee_id, |e: &Employee| e.id,
|_e| 0usize,
)
.penalize(|_id: &usize, count: &usize| SoftScore::of(*count as i64))
.named("complement with key")
}
#[test]
fn complement_with_key_skips_none_keyed_entities() {
let schedule = Schedule {
shifts: vec![
Shift {
employee_id: Some(0),
start: 0,
end: 1,
},
Shift {
employee_id: Some(0),
start: 1,
end: 2,
},
Shift {
employee_id: None, start: 2,
end: 3,
},
],
employees: vec![Employee { id: 0 }, Employee { id: 1 }],
};
let c = complement_skips_none_keys();
assert_eq!(c.evaluate(&schedule), SoftScore::of(-2));
assert_eq!(c.match_count(&schedule), 2);
}
fn comparison_condition_chain() -> impl IncrementalConstraint<Schedule, SoftScore> {
ConstraintFactory::<Schedule, SoftScore>::new()
.for_each(source(
shifts as fn(&Schedule) -> &[Shift],
ChangeSource::Descriptor(0),
))
.join((
source(
employees as fn(&Schedule) -> &[Employee],
ChangeSource::Descriptor(1),
),
less_than(|s: &Shift| s.start, |e: &Employee| e.id as i64),
))
.penalize(SoftScore::of(1))
.named("comparison chain")
}
#[test]
fn comparison_condition_composes_in_a_chain() {
let schedule = Schedule {
shifts: vec![
Shift {
employee_id: Some(0),
start: 5,
end: 6,
},
Shift {
employee_id: Some(0),
start: 1,
end: 2,
},
],
employees: vec![Employee { id: 0 }, Employee { id: 3 }],
};
let c = comparison_condition_chain();
assert_eq!(c.evaluate(&schedule), SoftScore::of(-1));
assert_eq!(c.match_count(&schedule), 1);
}
fn equality_plus_comparison() -> impl IncrementalConstraint<Schedule, SoftScore> {
ConstraintFactory::<Schedule, SoftScore>::new()
.for_each(source(
shifts as fn(&Schedule) -> &[Shift],
ChangeSource::Descriptor(0),
))
.join((
source(
employees as fn(&Schedule) -> &[Employee],
ChangeSource::Descriptor(1),
),
equal_bi(|s: &Shift| s.employee_id, |e: &Employee| Some(e.id))
.and(less_than(|s: &Shift| s.start, |_e: &Employee| 3i64)),
))
.penalize(SoftScore::of(1))
.named("equality plus comparison")
}
#[test]
fn equality_and_comparison_compose() {
let schedule = Schedule {
shifts: vec![
Shift {
employee_id: Some(0),
start: 5,
end: 6,
}, Shift {
employee_id: Some(0),
start: 1,
end: 2,
}, Shift {
employee_id: None,
start: 0,
end: 1,
}, ],
employees: vec![Employee { id: 0 }, Employee { id: 1 }],
};
let c = equality_plus_comparison();
assert_eq!(c.evaluate(&schedule), SoftScore::of(-1));
assert_eq!(c.match_count(&schedule), 1);
}
fn overlap_condition_chain() -> impl IncrementalConstraint<Schedule, SoftScore> {
ConstraintFactory::<Schedule, SoftScore>::new()
.for_each(source(
shifts as fn(&Schedule) -> &[Shift],
ChangeSource::Descriptor(0),
))
.join((
source(
employees as fn(&Schedule) -> &[Employee],
ChangeSource::Descriptor(1),
),
overlapping(
|s: &Shift| s.start,
|s: &Shift| s.end,
|e: &Employee| e.id as i64,
|e: &Employee| e.id as i64 + 2,
),
))
.penalize(SoftScore::of(1))
.named("overlap chain")
}
#[test]
fn overlap_condition_composes_in_a_chain() {
let schedule = Schedule {
shifts: vec![
Shift {
employee_id: Some(0),
start: 0,
end: 2,
}, Shift {
employee_id: Some(0),
start: 5,
end: 6,
}, ],
employees: vec![Employee { id: 0 }],
};
let c = overlap_condition_chain();
assert_eq!(c.evaluate(&schedule), SoftScore::of(-1));
assert_eq!(c.match_count(&schedule), 1);
}
type PairRow<'a> = crate::stream::relational::operator::Pair<
crate::stream::relational::Leaf<'a, Shift>,
crate::stream::relational::Leaf<'a, Employee>,
>;
fn pair_right_id(row: &PairRow<'_>) -> i64 {
row.right.entity.id as i64
}
fn pair_right_start(row: &PairRow<'_>) -> i64 {
row.left.entity.start
}
fn pair_right_end(row: &PairRow<'_>) -> i64 {
row.left.entity.end
}
fn leaf_employee_id(row: &crate::stream::relational::Leaf<'_, Employee>) -> i64 {
row.entity.id as i64
}
fn leaf_employee_end(row: &crate::stream::relational::Leaf<'_, Employee>) -> i64 {
row.entity.id as i64 + 2
}
fn later_arity_comparison() -> impl IncrementalConstraint<Schedule, SoftScore> {
ConstraintFactory::<Schedule, SoftScore>::new()
.for_each(source(
shifts as fn(&Schedule) -> &[Shift],
ChangeSource::Descriptor(0),
))
.join((
source(
employees as fn(&Schedule) -> &[Employee],
ChangeSource::Descriptor(1),
),
equal_bi(|s: &Shift| s.employee_id, |e: &Employee| Some(e.id)),
))
.join((
source(
employees as fn(&Schedule) -> &[Employee],
ChangeSource::Descriptor(1),
),
less_than(pair_right_id, leaf_employee_id),
))
.penalize(SoftScore::of(1))
.named("later arity comparison")
}
fn later_arity_overlap() -> impl IncrementalConstraint<Schedule, SoftScore> {
ConstraintFactory::<Schedule, SoftScore>::new()
.for_each(source(
shifts as fn(&Schedule) -> &[Shift],
ChangeSource::Descriptor(0),
))
.join((
source(
employees as fn(&Schedule) -> &[Employee],
ChangeSource::Descriptor(1),
),
equal_bi(|s: &Shift| s.employee_id, |e: &Employee| Some(e.id)),
))
.join((
source(
employees as fn(&Schedule) -> &[Employee],
ChangeSource::Descriptor(1),
),
overlapping(
pair_right_start,
pair_right_end,
leaf_employee_id,
leaf_employee_end,
),
))
.penalize(SoftScore::of(1))
.named("later arity overlap")
}
#[test]
fn comparison_composes_at_a_later_fluent_arity() {
let schedule = Schedule {
shifts: vec![
Shift {
employee_id: Some(0),
start: 0,
end: 2,
},
Shift {
employee_id: Some(1),
start: 0,
end: 2,
},
],
employees: vec![Employee { id: 0 }, Employee { id: 1 }, Employee { id: 2 }],
};
assert_eq!(
later_arity_comparison().evaluate(&schedule),
SoftScore::of(-3)
);
assert_eq!(later_arity_comparison().match_count(&schedule), 3);
}
#[test]
fn overlap_composes_at_a_later_fluent_arity() {
let schedule = Schedule {
shifts: vec![Shift {
employee_id: Some(0),
start: 0,
end: 2,
}],
employees: vec![Employee { id: 0 }, Employee { id: 1 }, Employee { id: 2 }],
};
assert_eq!(later_arity_overlap().evaluate(&schedule), SoftScore::of(-2));
assert_eq!(later_arity_overlap().match_count(&schedule), 2);
}