use super::support::*;
#[test]
fn projected_self_join_score_director_cached_score_matches_fresh_after_updates() {
let mut director = ScoreDirector::new(
projected_asymmetric_self_join_plan(),
(projected_asymmetric_self_join_constraint(),),
);
assert_projected_director_matches_fresh(&mut director);
for (entity_index, bucket, demand, enabled) in [
(2, 0, 40, true),
(1, 0, 20, false),
(1, 0, 5, true),
(0, 2, 2, true),
(2, 0, 30, false),
(2, 0, 30, true),
] {
director.before_variable_changed(0, entity_index);
{
let work = &mut director.working_solution_mut().work[entity_index];
work.bucket = bucket;
work.demand = demand;
work.enabled = enabled;
}
director.after_variable_changed(0, entity_index);
assert_projected_director_matches_fresh(&mut director);
}
}
#[test]
fn projected_self_join_nested_typed_undo_restores_cached_score() {
let initial_plan = projected_asymmetric_self_join_plan();
let mut director = ScoreDirector::new(
initial_plan.clone(),
(projected_asymmetric_self_join_constraint(),),
);
assert_projected_director_matches_fresh(&mut director);
let outer_score_state = director.snapshot_score_state();
let old_outer_work = director.working_solution().work[1].clone();
director.before_variable_changed(0, 1);
{
let work = &mut director.working_solution_mut().work[1];
work.bucket = 0;
work.demand = 5;
work.enabled = true;
}
director.after_variable_changed(0, 1);
assert_eq!(
director.calculate_score(),
fresh_projected_asymmetric_self_join_score(director.working_solution())
);
let nested_score_state = director.snapshot_score_state();
let old_nested_work = director.working_solution().work[2].clone();
director.before_variable_changed(0, 2);
{
let work = &mut director.working_solution_mut().work[2];
work.bucket = 0;
work.demand = 30;
work.enabled = false;
}
director.after_variable_changed(0, 2);
assert_eq!(
director.calculate_score(),
fresh_projected_asymmetric_self_join_score(director.working_solution())
);
director.before_variable_changed(0, 2);
director.working_solution_mut().work[2] = old_nested_work;
director.after_variable_changed(0, 2);
director.restore_score_state(nested_score_state);
assert_eq!(
director.calculate_score(),
fresh_projected_asymmetric_self_join_score(director.working_solution())
);
director.before_variable_changed(0, 1);
director.working_solution_mut().work[1] = old_outer_work;
director.after_variable_changed(0, 1);
director.restore_score_state(outer_score_state);
assert_eq!(director.working_solution().work, initial_plan.work);
assert_projected_director_matches_fresh(&mut director);
}
#[test]
fn projected_merged_descriptor_sources_update_only_owning_slot() {
let mut constraint = ConstraintFactory::<Plan, SoftScore>::new()
.for_each(source(
work as fn(&Plan) -> &[Work],
ChangeSource::Descriptor(0),
))
.project(WorkEntryProjection)
.merge(
ConstraintFactory::<Plan, SoftScore>::new()
.for_each(source(
capacity as fn(&Plan) -> &[Capacity],
ChangeSource::Descriptor(1),
))
.project(CapacityEntryProjection),
)
.group_by(
|entry: &Entry| entry.bucket,
sum(|entry: &Entry| entry.delta),
)
.penalize(|_bucket: &usize, delta: &i64| SoftScore::of((*delta).max(0)))
.named("capacity shortage");
let mut plan = Plan {
work: vec![Work {
bucket: 0,
demand: 5,
enabled: true,
}],
capacity: vec![Capacity {
bucket: 0,
capacity: 3,
}],
};
let mut total = constraint.initialize(&plan);
assert_eq!(total, SoftScore::of(-2));
total = total + constraint.on_retract(&plan, 0, 1);
plan.capacity[0].capacity = 8;
total = total + constraint.on_insert(&plan, 0, 1);
assert_eq!(total, SoftScore::of(0));
assert_eq!(total, constraint.evaluate(&plan));
}
#[test]
fn shared_projected_grouped_set_updates_one_projected_node_for_multiple_terminals() {
let projected = ConstraintFactory::<Plan, SoftScore>::new()
.for_each(source(
work as fn(&Plan) -> &[Work],
ChangeSource::Descriptor(0),
))
.project(WorkEntryProjection);
let state = ProjectedGroupedNodeState::new(
projected.source,
projected.filter,
|entry: &Entry| entry.bucket,
sum(|entry: &Entry| entry.delta),
);
let scorers = (
ProjectedGroupedTerminalScorer::new(
ConstraintRef::new("", "positive demand"),
ImpactType::Penalty,
|_bucket: &usize, demand: &i64| SoftScore::of((*demand).max(0)),
false,
),
ProjectedGroupedTerminalScorer::new(
ConstraintRef::new("", "weighted demand"),
ImpactType::Penalty,
|bucket: &usize, demand: &i64| SoftScore::of((*bucket as i64 + 1) * *demand),
false,
),
);
let mut constraints = SharedProjectedGroupedConstraintSet::new(state, scorers);
let mut plan = Plan {
work: vec![
Work {
bucket: 0,
demand: 5,
enabled: true,
},
Work {
bucket: 0,
demand: 7,
enabled: true,
},
],
capacity: Vec::new(),
};
assert_eq!(constraints.evaluate_all(&plan), SoftScore::of(-24));
assert_eq!(constraints.initialize_all(&plan), SoftScore::of(-24));
assert_eq!(constraints.constraint_count(), 2);
let metadata = constraints.constraint_metadata();
assert_eq!(metadata[0].name(), "positive demand");
assert_eq!(metadata[1].name(), "weighted demand");
let mut total = SoftScore::of(-24);
total = total + constraints.on_retract_all(&plan, 1, 0);
plan.work[1].demand = 2;
total = total + constraints.on_insert_all(&plan, 1, 0);
assert_eq!(constraints.state().update_count(), 2);
assert_eq!(constraints.state().changed_key_count(), 2);
assert_eq!(total, SoftScore::of(-14));
assert_eq!(total, constraints.evaluate_all(&plan));
}
fn projected_demand_complement_constraint() -> impl IncrementalConstraint<Plan, SoftScore> {
ConstraintFactory::<Plan, SoftScore>::new()
.for_each(source(
work as fn(&Plan) -> &[Work],
ChangeSource::Descriptor(0),
))
.project(WorkEntryProjection)
.group_by(
|entry: &Entry| entry.bucket,
sum(|entry: &Entry| entry.delta),
)
.complement(
source(
capacity as fn(&Plan) -> &[Capacity],
ChangeSource::Descriptor(1),
),
|capacity: &Capacity| capacity.bucket,
|_capacity: &Capacity| 3i64,
)
.penalize(|bucket: &usize, demand: &i64| SoftScore::of((*bucket as i64 * 10) + *demand))
.named("projected demand by capacity bucket")
}
fn projected_demand_complement_plan() -> Plan {
Plan {
work: vec![Work {
bucket: 0,
demand: 5,
enabled: true,
}],
capacity: vec![
Capacity {
bucket: 0,
capacity: 0,
},
Capacity {
bucket: 1,
capacity: 0,
},
],
}
}
#[test]
fn projected_group_by_complement_scores_missing_projected_keys() {
let constraint = projected_demand_complement_constraint();
let plan = projected_demand_complement_plan();
assert_eq!(constraint.match_count(&plan), 2);
assert_eq!(constraint.evaluate(&plan), SoftScore::of(-18));
}
#[test]
fn projected_group_by_complement_updates_projected_rows() {
let mut constraint = projected_demand_complement_constraint();
let mut plan = projected_demand_complement_plan();
let mut total = constraint.initialize(&plan);
assert_eq!(total, SoftScore::of(-18));
total = total + constraint.on_retract(&plan, 0, 0);
plan.work[0].demand = 7;
total = total + constraint.on_insert(&plan, 0, 0);
assert_eq!(total, SoftScore::of(-20));
assert_eq!(total, constraint.evaluate(&plan));
}
#[test]
fn projected_group_by_complement_updates_complement_keys() {
let mut constraint = projected_demand_complement_constraint();
let mut plan = projected_demand_complement_plan();
let mut total = constraint.initialize(&plan);
assert_eq!(total, SoftScore::of(-18));
total = total + constraint.on_retract(&plan, 1, 1);
plan.capacity[1].bucket = 2;
total = total + constraint.on_insert(&plan, 1, 1);
assert_eq!(total, SoftScore::of(-28));
assert_eq!(total, constraint.evaluate(&plan));
}
#[test]
fn projected_group_by_complement_duplicate_complement_keys_match_incremental() {
let mut constraint = projected_demand_complement_constraint();
let mut plan = Plan {
work: vec![Work {
bucket: 0,
demand: 5,
enabled: true,
}],
capacity: vec![
Capacity {
bucket: 0,
capacity: 0,
},
Capacity {
bucket: 0,
capacity: 0,
},
Capacity {
bucket: 1,
capacity: 0,
},
],
};
let mut total = constraint.initialize(&plan);
assert_eq!(total, SoftScore::of(-23));
assert_eq!(total, constraint.evaluate(&plan));
total = total + constraint.on_retract(&plan, 0, 0);
plan.work[0].demand = 7;
total = total + constraint.on_insert(&plan, 0, 0);
assert_eq!(total, SoftScore::of(-27));
assert_eq!(total, constraint.evaluate(&plan));
total = total + constraint.on_retract(&plan, 1, 1);
plan.capacity[1].bucket = 2;
total = total + constraint.on_insert(&plan, 1, 1);
assert_eq!(total, SoftScore::of(-43));
assert_eq!(total, constraint.evaluate(&plan));
}
#[test]
fn shared_projected_group_by_complement_updates_one_node_for_multiple_terminals() {
let complemented = ConstraintFactory::<Plan, SoftScore>::new()
.for_each(source(
work as fn(&Plan) -> &[Work],
ChangeSource::Descriptor(0),
))
.project(WorkEntryProjection)
.group_by(
|entry: &Entry| entry.bucket,
sum(|entry: &Entry| entry.delta),
)
.complement(
source(
capacity as fn(&Plan) -> &[Capacity],
ChangeSource::Descriptor(1),
),
|capacity: &Capacity| capacity.bucket,
|_capacity: &Capacity| 3i64,
);
let state = ProjectedComplementedGroupedNodeState::new(
complemented.source,
complemented.extractor_b,
complemented.filter,
complemented.key_a,
complemented.key_b,
complemented.collector,
complemented.default_fn,
);
let scorers = (
ProjectedComplementedGroupedTerminalScorer::new(
ConstraintRef::new("", "projected demand"),
ImpactType::Penalty,
|bucket: &usize, demand: &i64| SoftScore::of((*bucket as i64 * 10) + *demand),
false,
),
ProjectedComplementedGroupedTerminalScorer::new(
ConstraintRef::new("", "double projected demand"),
ImpactType::Penalty,
|_bucket: &usize, demand: &i64| SoftScore::of(*demand * 2),
false,
),
);
let mut constraints = SharedProjectedComplementedGroupedConstraintSet::new(state, scorers);
let mut plan = projected_demand_complement_plan();
let mut total = constraints.initialize_all(&plan);
assert_eq!(total, SoftScore::of(-34));
total = total + constraints.on_retract_all(&plan, 0, 0);
plan.work[0].demand = 7;
total = total + constraints.on_insert_all(&plan, 0, 0);
assert_eq!(constraints.state().update_count(), 2);
assert_eq!(total, SoftScore::of(-40));
assert_eq!(total, constraints.evaluate_all(&plan));
assert_eq!(constraints.evaluate_each(&plan).len(), 2);
}
#[test]
fn projected_merged_descriptor_sources_keep_same_entity_index_slots_distinct() {
let mut constraint = ConstraintFactory::<Plan, SoftScore>::new()
.for_each(source(
work as fn(&Plan) -> &[Work],
ChangeSource::Descriptor(0),
))
.project(WorkEntryProjection)
.merge(
ConstraintFactory::<Plan, SoftScore>::new()
.for_each(source(
capacity as fn(&Plan) -> &[Capacity],
ChangeSource::Descriptor(1),
))
.project(CapacityEntryProjection),
)
.penalize(|entry: &Entry| SoftScore::of(entry.delta))
.named("merged projected rows");
let mut plan = Plan {
work: vec![Work {
bucket: 0,
demand: 5,
enabled: true,
}],
capacity: vec![Capacity {
bucket: 0,
capacity: 3,
}],
};
let mut total = constraint.initialize(&plan);
assert_eq!(total, SoftScore::of(-2));
total = total + constraint.on_retract(&plan, 0, 0);
plan.work[0].demand = 8;
total = total + constraint.on_insert(&plan, 0, 0);
assert_eq!(total, SoftScore::of(-5));
assert_eq!(total, constraint.evaluate(&plan));
}
#[test]
#[should_panic(expected = "cannot localize entity indexes")]
fn projected_unknown_source_panics_on_localized_callback() {
let mut constraint = ConstraintFactory::<Plan, SoftScore>::new()
.for_each(work as fn(&Plan) -> &[Work])
.project(WorkEntryProjection)
.penalize(|entry: &Entry| SoftScore::of(entry.delta))
.named("unknown projected");
let plan = Plan {
work: vec![Work {
bucket: 0,
demand: 5,
enabled: true,
}],
capacity: Vec::new(),
};
constraint.initialize(&plan);
constraint.on_retract(&plan, 0, 0);
}