solverforge_solver/builder/selector/
coverage_repair.rs1use crate::builder::context::CoverageGroupBinding;
2use crate::phase::construction::coverage::{
3 capacity_conflict_moves, required_coverage_moves, CoverageMoveOptions,
4};
5
6const DEFAULT_MAX_MOVES_PER_STEP: usize = 256;
7
8pub struct CoverageRepairSelector<S> {
9 group: CoverageGroupBinding<S>,
10 value_candidate_limit: Option<usize>,
11 max_moves_per_step: Option<usize>,
12 require_hard_improvement: bool,
13}
14
15impl<S> CoverageRepairSelector<S> {
16 pub fn new(
17 group: CoverageGroupBinding<S>,
18 value_candidate_limit: Option<usize>,
19 max_moves_per_step: Option<usize>,
20 require_hard_improvement: bool,
21 ) -> Self {
22 Self {
23 group,
24 value_candidate_limit,
25 max_moves_per_step,
26 require_hard_improvement,
27 }
28 }
29
30 fn max_moves_per_step(&self) -> usize {
31 self.max_moves_per_step
32 .or(self.group.limits.max_moves_per_step)
33 .unwrap_or(DEFAULT_MAX_MOVES_PER_STEP)
34 }
35}
36
37impl<S> std::fmt::Debug for CoverageRepairSelector<S> {
38 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39 f.debug_struct("CoverageRepairSelector")
40 .field("group_name", &self.group.group_name)
41 .field("value_candidate_limit", &self.value_candidate_limit)
42 .field("max_moves_per_step", &self.max_moves_per_step())
43 .field("require_hard_improvement", &self.require_hard_improvement)
44 .finish()
45 }
46}
47
48pub struct CoverageRepairCursor<S>
49where
50 S: PlanningSolution + 'static,
51{
52 store: CandidateStore<S, ScalarMoveUnion<S, usize>>,
53 next_index: usize,
54}
55
56impl<S> CoverageRepairCursor<S>
57where
58 S: PlanningSolution + 'static,
59{
60 fn new(store: CandidateStore<S, ScalarMoveUnion<S, usize>>) -> Self {
61 Self {
62 store,
63 next_index: 0,
64 }
65 }
66}
67
68impl<S> MoveCursor<S, ScalarMoveUnion<S, usize>> for CoverageRepairCursor<S>
69where
70 S: PlanningSolution + 'static,
71{
72 fn next_candidate(&mut self) -> Option<CandidateId> {
73 if self.next_index >= self.store.len() {
74 return None;
75 }
76 let id = CandidateId::new(self.next_index);
77 self.next_index += 1;
78 Some(id)
79 }
80
81 fn candidate(
82 &self,
83 id: CandidateId,
84 ) -> Option<MoveCandidateRef<'_, S, ScalarMoveUnion<S, usize>>> {
85 self.store.candidate(id)
86 }
87
88 fn take_candidate(&mut self, id: CandidateId) -> ScalarMoveUnion<S, usize> {
89 self.store.take_candidate(id)
90 }
91}
92
93impl<S> MoveSelector<S, ScalarMoveUnion<S, usize>> for CoverageRepairSelector<S>
94where
95 S: PlanningSolution + 'static,
96{
97 type Cursor<'a>
98 = CoverageRepairCursor<S>
99 where
100 Self: 'a;
101
102 fn open_cursor<'a, D: solverforge_scoring::Director<S>>(
103 &'a self,
104 score_director: &D,
105 ) -> Self::Cursor<'a> {
106 let solution = score_director.working_solution();
107 let max_moves_per_step = self.max_moves_per_step();
108 let options = CoverageMoveOptions::for_repair(
109 &self.group,
110 self.value_candidate_limit,
111 max_moves_per_step,
112 );
113 let mut store = CandidateStore::with_capacity(max_moves_per_step);
114 for mov in required_coverage_moves(&self.group, solution, options)
115 .into_iter()
116 .chain(capacity_conflict_moves(&self.group, solution, options))
117 {
118 if store.len() >= max_moves_per_step {
119 break;
120 }
121 let mov = mov.with_require_hard_improvement(self.require_hard_improvement);
122 if mov.is_doable(score_director) {
123 store.push(ScalarMoveUnion::CompoundScalar(mov));
124 }
125 }
126 CoverageRepairCursor::new(store)
127 }
128
129 fn size<D: solverforge_scoring::Director<S>>(&self, _score_director: &D) -> usize {
130 self.max_moves_per_step()
131 }
132}