Spaces:
Sleeping
Sleeping
File size: 4,944 Bytes
4b94493 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | 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");
}
|