solverforge_solver/builder/selector/
grouped_scalar.rs1pub struct GroupedScalarSelector<S> {
2 group: crate::builder::context::ScalarGroupBinding<S>,
3 value_candidate_limit: Option<usize>,
4 max_moves_per_step: usize,
5 require_hard_improvement: bool,
6}
7
8impl<S> GroupedScalarSelector<S> {
9 pub fn new(
10 group: crate::builder::context::ScalarGroupBinding<S>,
11 value_candidate_limit: Option<usize>,
12 max_moves_per_step: Option<usize>,
13 require_hard_improvement: bool,
14 ) -> Self {
15 let effective_value_candidate_limit =
16 value_candidate_limit.or(group.limits.value_candidate_limit);
17 let effective_max_moves_per_step = max_moves_per_step
18 .or(group.limits.max_moves_per_step)
19 .unwrap_or(256);
20 Self {
21 group,
22 value_candidate_limit: effective_value_candidate_limit,
23 max_moves_per_step: effective_max_moves_per_step,
24 require_hard_improvement,
25 }
26 }
27
28 fn limits(&self) -> crate::builder::context::ScalarGroupLimits {
29 crate::builder::context::ScalarGroupLimits {
30 value_candidate_limit: self.value_candidate_limit,
31 group_candidate_limit: None,
32 max_moves_per_step: Some(self.max_moves_per_step),
33 max_augmenting_depth: None,
34 max_rematch_size: None,
35 }
36 }
37}
38
39impl<S> std::fmt::Debug for GroupedScalarSelector<S> {
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 f.debug_struct("GroupedScalarSelector")
42 .field("group_name", &self.group.group_name)
43 .field("value_candidate_limit", &self.value_candidate_limit)
44 .field("max_moves_per_step", &self.max_moves_per_step)
45 .field("require_hard_improvement", &self.require_hard_improvement)
46 .finish()
47 }
48}
49
50pub struct GroupedScalarCursor<S>
51where
52 S: PlanningSolution + 'static,
53{
54 store: CandidateStore<S, ScalarMoveUnion<S, usize>>,
55 next_index: usize,
56}
57
58impl<S> GroupedScalarCursor<S>
59where
60 S: PlanningSolution + 'static,
61{
62 fn new(store: CandidateStore<S, ScalarMoveUnion<S, usize>>) -> Self {
63 Self {
64 store,
65 next_index: 0,
66 }
67 }
68}
69
70impl<S> MoveCursor<S, ScalarMoveUnion<S, usize>> for GroupedScalarCursor<S>
71where
72 S: PlanningSolution + 'static,
73{
74 fn next_candidate(&mut self) -> Option<CandidateId> {
75 if self.next_index >= self.store.len() {
76 return None;
77 }
78 let id = CandidateId::new(self.next_index);
79 self.next_index += 1;
80 Some(id)
81 }
82
83 fn candidate(
84 &self,
85 id: CandidateId,
86 ) -> Option<MoveCandidateRef<'_, S, ScalarMoveUnion<S, usize>>> {
87 self.store.candidate(id)
88 }
89
90 fn take_candidate(&mut self, id: CandidateId) -> ScalarMoveUnion<S, usize> {
91 self.store.take_candidate(id)
92 }
93}
94
95impl<S> MoveSelector<S, ScalarMoveUnion<S, usize>> for GroupedScalarSelector<S>
96where
97 S: PlanningSolution + 'static,
98{
99 type Cursor<'a>
100 = GroupedScalarCursor<S>
101 where
102 Self: 'a;
103
104 fn open_cursor<'a, D: solverforge_scoring::Director<S>>(
105 &'a self,
106 score_director: &D,
107 ) -> Self::Cursor<'a> {
108 let candidate_provider = match self.group.kind {
109 crate::builder::ScalarGroupBindingKind::Candidates { candidate_provider } => {
110 candidate_provider
111 }
112 crate::builder::ScalarGroupBindingKind::Assignment(assignment) => {
113 return self.open_assignment_cursor(score_director, assignment);
114 }
115 };
116 let solution = score_director.working_solution();
117 let mut store = CandidateStore::with_capacity(self.max_moves_per_step);
118 let mut seen_candidates = Vec::new();
119 let mut targets = std::collections::HashSet::new();
120
121 for candidate in candidate_provider(solution, self.limits()) {
122 if store.len() >= self.max_moves_per_step {
123 break;
124 }
125 if candidate.edits().is_empty()
126 || seen_candidates
127 .iter()
128 .any(|seen_candidate| seen_candidate == &candidate)
129 {
130 continue;
131 }
132 targets.clear();
133 if candidate.edits().iter().any(|edit| {
134 !targets.insert((
135 edit.descriptor_index(),
136 edit.entity_index(),
137 edit.variable_name(),
138 ))
139 }) {
140 continue;
141 }
142
143 let Some(mov) = compound_move_for_group_candidate(&self.group, solution, &candidate)
144 else {
145 continue;
146 };
147 let mov = mov.with_require_hard_improvement(self.require_hard_improvement);
148 if mov.is_doable(score_director) {
149 store.push(ScalarMoveUnion::CompoundScalar(mov));
150 seen_candidates.push(candidate);
151 }
152 }
153
154 GroupedScalarCursor::new(store)
155 }
156
157 fn size<D: solverforge_scoring::Director<S>>(&self, _score_director: &D) -> usize {
158 self.max_moves_per_step
159 }
160}
161
162impl<S> GroupedScalarSelector<S>
163where
164 S: PlanningSolution + 'static,
165{
166 fn open_assignment_cursor<D: solverforge_scoring::Director<S>>(
167 &self,
168 score_director: &D,
169 assignment: crate::builder::ScalarAssignmentBinding<S>,
170 ) -> GroupedScalarCursor<S> {
171 let solution = score_director.working_solution();
172 let options = crate::phase::construction::grouped_scalar::ScalarAssignmentMoveOptions::for_selector(
173 self.group.limits,
174 self.value_candidate_limit,
175 self.max_moves_per_step,
176 );
177 let mut store = CandidateStore::with_capacity(self.max_moves_per_step);
178 for mov in crate::phase::construction::grouped_scalar::required_assignment_moves(
179 &assignment,
180 solution,
181 options,
182 )
183 .into_iter()
184 .chain(crate::phase::construction::grouped_scalar::capacity_conflict_moves(
185 &assignment,
186 solution,
187 options,
188 ))
189 .chain(crate::phase::construction::grouped_scalar::reassignment_moves(
190 &assignment,
191 solution,
192 options,
193 ))
194 .chain(crate::phase::construction::grouped_scalar::rematch_assignment_moves(
195 &assignment,
196 solution,
197 options,
198 )) {
199 if store.len() >= self.max_moves_per_step {
200 break;
201 }
202 let mov = mov.with_require_hard_improvement(self.require_hard_improvement);
203 if mov.is_doable(score_director) {
204 store.push(ScalarMoveUnion::CompoundScalar(mov));
205 }
206 }
207 GroupedScalarCursor::new(store)
208 }
209}
210
211fn compound_move_for_group_candidate<S>(
212 group: &crate::builder::context::ScalarGroupBinding<S>,
213 solution: &S,
214 candidate: &crate::builder::context::ScalarCandidate<S>,
215) -> Option<crate::heuristic::r#move::CompoundScalarMove<S>>
216where
217 S: PlanningSolution + 'static,
218{
219 let reason = candidate.reason();
220 let mut edits = Vec::with_capacity(candidate.edits().len());
221 for edit in candidate.edits() {
222 let member = group.member_for_edit(edit)?;
223 if !member.value_is_legal(solution, edit.entity_index(), edit.to_value()) {
224 return None;
225 }
226 edits.push(crate::heuristic::r#move::CompoundScalarEdit {
227 descriptor_index: member.descriptor_index,
228 entity_index: edit.entity_index(),
229 variable_index: member.variable_index,
230 variable_name: member.variable_name,
231 to_value: edit.to_value(),
232 getter: member.getter,
233 setter: member.setter,
234 value_is_legal: None,
235 });
236 }
237
238 Some(crate::heuristic::r#move::CompoundScalarMove::with_label(
239 reason,
240 crate::heuristic::r#move::COMPOUND_SCALAR_VARIABLE,
241 edits,
242 ))
243}