Remove min by shift type boundaries, improve logging, add more tests, add logs in tests, move fixtures in separate file, move restrictions_violated logic into valid_residents of next slot
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use crate::{
|
||||
config::UserConfig,
|
||||
config::{ToxicPair, UserConfig},
|
||||
errors::SearchError,
|
||||
resident::ResidentId,
|
||||
schedule::MonthlySchedule,
|
||||
@@ -10,7 +10,6 @@ use crate::{
|
||||
workload::{WorkloadBounds, WorkloadTracker},
|
||||
};
|
||||
|
||||
use log::info;
|
||||
use rand::{rngs::SmallRng, seq::SliceRandom, SeedableRng};
|
||||
use rayon::{
|
||||
current_thread_index,
|
||||
@@ -60,7 +59,7 @@ impl Scheduler {
|
||||
.map(Slot::from)
|
||||
.ok_or(SearchError::ScheduleFull)?;
|
||||
|
||||
let resident_ids = self.valid_residents(slot, schedule);
|
||||
let resident_ids = self.valid_residents(slot, schedule, tracker);
|
||||
let solved_in_thread = AtomicBool::new(false);
|
||||
|
||||
let sovled_state = resident_ids.par_iter().find_map_any(|&id| {
|
||||
@@ -81,7 +80,8 @@ impl Scheduler {
|
||||
Ok(true) => Some((local_schedule, local_tracker)),
|
||||
Ok(false) => None,
|
||||
Err(e) => {
|
||||
info!("Thread Id: [{}] {}", current_thread_index().unwrap(), e);
|
||||
let thread_id = current_thread_index().unwrap();
|
||||
log::log!(e.log_level(), "Thread Id: [{}] {}", thread_id, e);
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -114,18 +114,13 @@ impl Scheduler {
|
||||
return Err(SearchError::Timeout);
|
||||
}
|
||||
|
||||
if !slot.is_first()
|
||||
&& schedule.restrictions_violated(&slot.previous(), &self.config, &self.bounds, tracker)
|
||||
{
|
||||
if schedule.has_resident_in_consecutive_days(&slot.previous()) {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
if slot.greater_than(self.config.total_days) {
|
||||
if tracker.are_all_thresholds_met(&self.config, &self.bounds) {
|
||||
solved_in_thread.store(true, Ordering::Relaxed);
|
||||
return Ok(true);
|
||||
}
|
||||
return Ok(false);
|
||||
if self.found_solution(slot) {
|
||||
solved_in_thread.store(true, Ordering::Relaxed);
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
if schedule.is_slot_manually_assigned(&slot) {
|
||||
@@ -133,7 +128,7 @@ impl Scheduler {
|
||||
}
|
||||
|
||||
let mut rng = SmallRng::from_rng(&mut rand::rng());
|
||||
let mut valid_resident_ids = self.valid_residents(slot, schedule);
|
||||
let mut valid_resident_ids = self.valid_residents(slot, schedule, tracker);
|
||||
valid_resident_ids.shuffle(&mut rng);
|
||||
valid_resident_ids.sort_by_key(|res_id| {
|
||||
let type_count = tracker.get_type_count(res_id, slot.shift_type());
|
||||
@@ -156,10 +151,19 @@ impl Scheduler {
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
pub fn found_solution(&self, slot: Slot) -> bool {
|
||||
slot.greater_than(self.config.total_days)
|
||||
}
|
||||
|
||||
/// Return all valid residents for the current slot
|
||||
pub fn valid_residents(&self, slot: Slot, schedule: &MonthlySchedule) -> Vec<ResidentId> {
|
||||
let required_type = slot.shift_type();
|
||||
let other_resident = slot
|
||||
pub fn valid_residents(
|
||||
&self,
|
||||
slot: Slot,
|
||||
schedule: &MonthlySchedule,
|
||||
tracker: &WorkloadTracker,
|
||||
) -> Vec<ResidentId> {
|
||||
let is_holiday_slot = self.config.is_holiday_or_weekend_slot(slot.day.0);
|
||||
let other_resident_id = slot
|
||||
.other_position()
|
||||
.and_then(|partner_slot| schedule.get_resident_id(&partner_slot));
|
||||
|
||||
@@ -167,9 +171,26 @@ impl Scheduler {
|
||||
.residents
|
||||
.iter()
|
||||
.filter(|r| {
|
||||
Some(&r.id) != other_resident
|
||||
&& !r.negative_shifts.contains(&slot.day)
|
||||
&& r.allowed_types.contains(&required_type)
|
||||
if let Some(other_id) = other_resident_id {
|
||||
if &r.id == other_id {
|
||||
return false;
|
||||
}
|
||||
|
||||
if self
|
||||
.config
|
||||
.toxic_pairs
|
||||
.iter()
|
||||
.any(|tp| tp.matches(&ToxicPair::from((r.id, *other_id))))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
!r.negative_shifts.contains(&slot.day)
|
||||
&& r.allowed_types.contains(&slot.shift_type())
|
||||
&& !tracker.reached_workload_limit(&self.bounds, &r.id)
|
||||
&& (!is_holiday_slot || !tracker.reached_holiday_limit(&self.bounds, &r.id))
|
||||
&& !tracker.reached_shift_type_limit(&self.bounds, &r.id, &slot)
|
||||
})
|
||||
.map(|r| r.id)
|
||||
.collect()
|
||||
@@ -185,7 +206,6 @@ mod tests {
|
||||
resident::Resident,
|
||||
schedule::MonthlySchedule,
|
||||
scheduler::Scheduler,
|
||||
slot::{Day, ShiftPosition, Slot},
|
||||
workload::{WorkloadBounds, WorkloadTracker},
|
||||
};
|
||||
|
||||
@@ -197,12 +217,12 @@ mod tests {
|
||||
#[fixture]
|
||||
fn config() -> UserConfig {
|
||||
UserConfig::default().with_residents(vec![
|
||||
Resident::new(1, "Stefanos"),
|
||||
Resident::new(2, "Iordanis"),
|
||||
Resident::new(3, "Maria"),
|
||||
Resident::new(4, "Veatriki"),
|
||||
Resident::new(5, "Takis"),
|
||||
Resident::new(6, "Akis"),
|
||||
Resident::new(1, "R1"),
|
||||
Resident::new(2, "R2"),
|
||||
Resident::new(3, "R3"),
|
||||
Resident::new(4, "R4"),
|
||||
Resident::new(5, "R5"),
|
||||
Resident::new(6, "R6"),
|
||||
])
|
||||
}
|
||||
|
||||
@@ -227,25 +247,8 @@ mod tests {
|
||||
mut tracker: WorkloadTracker,
|
||||
scheduler: Scheduler,
|
||||
) {
|
||||
assert!(scheduler.run(&mut schedule, &mut tracker).is_ok());
|
||||
|
||||
for d in 1..=scheduler.config.total_days {
|
||||
let day = Day(d);
|
||||
if day.is_open_shift() {
|
||||
let slot_first = Slot::new(day, ShiftPosition::First);
|
||||
assert!(schedule.get_resident_id(&slot_first).is_some());
|
||||
let slot_second = Slot::new(day, ShiftPosition::Second);
|
||||
assert!(schedule.get_resident_id(&slot_second).is_some());
|
||||
} else {
|
||||
let slot_first = Slot::new(day, ShiftPosition::First);
|
||||
assert!(schedule.get_resident_id(&slot_first).is_some());
|
||||
}
|
||||
}
|
||||
|
||||
for r in &scheduler.config.residents {
|
||||
let workload = tracker.current_workload(&r.id);
|
||||
let limit = *scheduler.bounds.max_workloads.get(&r.id).unwrap();
|
||||
assert!(workload <= limit);
|
||||
}
|
||||
let solved = scheduler.run(&mut schedule, &mut tracker);
|
||||
assert!(solved.is_ok());
|
||||
assert!(solved.unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user