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