solverforge-lessons / src /data /data_seed /solve_tests.rs
github-actions[bot]
chore: sync uc-lessons Space
4b94493
Raw
History Blame Contribute Delete
4.94 kB
use solverforge::{ConstraintSet, SolverEvent, SolverManager};
use std::collections::BTreeSet;
use super::{generate, DemoData};
use crate::domain::Plan;
// Static manager — must be 'static for retained job execution.
static MANAGER: SolverManager<Plan> = SolverManager::new();
fn schedule() -> Plan {
generate(DemoData::Large)
}
/// Slow end-to-end acceptance test for the large dataset.
///
/// This verifies that the solver starts from an unassigned schedule, reaches a
/// hard/medium-feasible timetable, and keeps a visible soft optimization score.
#[test]
#[ignore = "slow acceptance test for the large dataset"]
fn large_demo_solves_to_feasible_progressing_schedule() {
let plan = schedule();
let initial_score = crate::constraints::create_constraints().evaluate_all(&plan);
assert_eq!(
initial_score,
solverforge::HardMediumSoftScore::of_medium(-600),
"The generated demo must start unassigned, not already solved"
);
let (job_id, mut receiver) = MANAGER.solve(plan).expect("job should start");
let mut completed_score = None;
let mut completed_solution = None;
let mut observed_scores = Vec::new();
while let Some(event) = receiver.blocking_recv() {
if let Some(score) = event.metadata().current_score {
observed_scores.push(score);
}
match event {
SolverEvent::Completed { solution, .. } => {
completed_score = solution.score;
completed_solution = Some(solution);
break;
}
SolverEvent::Failed { error, .. } => {
panic!("demo solve failed unexpectedly: {error}");
}
_ => {}
}
}
let score = completed_score.expect("expected a completed score");
let solution = completed_solution.expect("expected a completed solution");
// The best solution must satisfy both the hard feasibility rules and the
// medium-level assignment requirements while retaining soft optimization
// pressure that lets the UI show continued score movement.
assert_eq!(
score.hard(),
0,
"Expected hard-feasible solution, but got: {}",
score
);
assert_eq!(
score.medium(),
0,
"Expected all lessons assigned, but got: {}",
score
);
assert!(
score.soft() < 0,
"Expected remaining soft penalties for realistic timetable quality, got: {}",
score
);
assert!(
score.medium() > initial_score.medium(),
"Expected terminal score to improve from the unassigned medium penalty"
);
assert!(
observed_scores.contains(&initial_score),
"Expected event stream to expose the unassigned initial score"
);
assert!(
observed_scores
.iter()
.any(|score| score.hard() == 0 && score.medium() == 0 && score.soft() < 0),
"Expected event stream to expose a feasible soft-scored timetable"
);
let lesson_count = solution.lessons.len();
let assigned_timeslots = solution
.lessons
.iter()
.filter(|l| l.timeslot_idx.is_some())
.count();
let assigned_rooms = solution
.lessons
.iter()
.filter(|l| l.room_idx.is_some())
.count();
assert_eq!(
assigned_timeslots, lesson_count,
"Every lesson must have a timeslot assignment"
);
assert_eq!(
assigned_rooms, lesson_count,
"Every lesson must have a room assignment"
);
let distinct_timeslots = solution
.lessons
.iter()
.filter_map(|lesson| lesson.timeslot_idx)
.collect::<BTreeSet<_>>()
.len();
let distinct_rooms = solution
.lessons
.iter()
.filter_map(|lesson| lesson.room_idx)
.collect::<BTreeSet<_>>()
.len();
assert!(
distinct_timeslots > 1,
"The solved timetable must not collapse every lesson into one timeslot"
);
assert!(
distinct_rooms > 1,
"The solved timetable must not collapse every lesson into one room"
);
let constraints = crate::constraints::create_constraints();
let hard_or_medium_constraints: Vec<_> = constraints
.evaluate_detailed(&solution)
.into_iter()
.filter(|analysis| analysis.score.hard() != 0 || analysis.score.medium() != 0)
.map(|analysis| format!("{}={}", analysis.constraint_ref.name, analysis.score))
.collect();
assert!(
hard_or_medium_constraints.is_empty(),
"Expected all hard/medium constraints to score zero, got: {}",
hard_or_medium_constraints.join(", ")
);
eprintln!(
"Solution: {} lessons, {} timeslots assigned, {} rooms assigned, score {}",
lesson_count, assigned_timeslots, assigned_rooms, score
);
MANAGER.delete(job_id).expect("delete completed job");
}