solverforge_solver/builder/selector/
grouped_scalar.rs1pub struct GroupedScalarSelector<S> {
2 group: crate::builder::context::ScalarGroupContext<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::ScalarGroupContext<S>,
11 value_candidate_limit: Option<usize>,
12 max_moves_per_step: Option<usize>,
13 require_hard_improvement: bool,
14 ) -> Self {
15 Self {
16 group,
17 value_candidate_limit,
18 max_moves_per_step: max_moves_per_step.unwrap_or(256),
19 require_hard_improvement,
20 }
21 }
22
23 fn limits(&self) -> crate::builder::context::ScalarGroupLimits {
24 crate::builder::context::ScalarGroupLimits {
25 value_candidate_limit: self.value_candidate_limit,
26 group_candidate_limit: None,
27 max_moves_per_step: Some(self.max_moves_per_step),
28 }
29 }
30}
31
32impl<S> std::fmt::Debug for GroupedScalarSelector<S> {
33 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 f.debug_struct("GroupedScalarSelector")
35 .field("group_name", &self.group.group_name)
36 .field("value_candidate_limit", &self.value_candidate_limit)
37 .field("max_moves_per_step", &self.max_moves_per_step)
38 .field("require_hard_improvement", &self.require_hard_improvement)
39 .finish()
40 }
41}
42
43pub struct GroupedScalarCursor<S>
44where
45 S: PlanningSolution + 'static,
46{
47 store: CandidateStore<S, ScalarMoveUnion<S, usize>>,
48 next_index: usize,
49}
50
51impl<S> GroupedScalarCursor<S>
52where
53 S: PlanningSolution + 'static,
54{
55 fn new(store: CandidateStore<S, ScalarMoveUnion<S, usize>>) -> Self {
56 Self {
57 store,
58 next_index: 0,
59 }
60 }
61}
62
63impl<S> MoveCursor<S, ScalarMoveUnion<S, usize>> for GroupedScalarCursor<S>
64where
65 S: PlanningSolution + 'static,
66{
67 fn next_candidate(&mut self) -> Option<CandidateId> {
68 if self.next_index >= self.store.len() {
69 return None;
70 }
71 let id = CandidateId::new(self.next_index);
72 self.next_index += 1;
73 Some(id)
74 }
75
76 fn candidate(
77 &self,
78 id: CandidateId,
79 ) -> Option<MoveCandidateRef<'_, S, ScalarMoveUnion<S, usize>>> {
80 self.store.candidate(id)
81 }
82
83 fn take_candidate(&mut self, id: CandidateId) -> ScalarMoveUnion<S, usize> {
84 self.store.take_candidate(id)
85 }
86}
87
88impl<S> MoveSelector<S, ScalarMoveUnion<S, usize>> for GroupedScalarSelector<S>
89where
90 S: PlanningSolution + 'static,
91{
92 type Cursor<'a>
93 = GroupedScalarCursor<S>
94 where
95 Self: 'a;
96
97 fn open_cursor<'a, D: solverforge_scoring::Director<S>>(
98 &'a self,
99 score_director: &D,
100 ) -> Self::Cursor<'a> {
101 let solution = score_director.working_solution();
102 let mut store = CandidateStore::with_capacity(self.max_moves_per_step);
103 let mut seen = std::collections::HashSet::new();
104 let mut targets = std::collections::HashSet::new();
105
106 for candidate in (self.group.candidate_provider)(solution, self.limits()) {
107 if store.len() >= self.max_moves_per_step {
108 break;
109 }
110 if candidate.edits.is_empty() || !seen.insert(candidate.clone()) {
111 continue;
112 }
113 targets.clear();
114 if candidate.edits.iter().any(|edit| {
115 !targets.insert((edit.descriptor_index, edit.entity_index, edit.variable_name))
116 }) {
117 continue;
118 }
119
120 let Some(mov) = compound_move_for_group_candidate(&self.group, solution, candidate)
121 else {
122 continue;
123 };
124 let mov = mov.with_require_hard_improvement(self.require_hard_improvement);
125 if mov.is_doable(score_director) {
126 store.push(ScalarMoveUnion::CompoundScalar(mov));
127 }
128 }
129
130 GroupedScalarCursor::new(store)
131 }
132
133 fn size<D: solverforge_scoring::Director<S>>(&self, _score_director: &D) -> usize {
134 self.max_moves_per_step
135 }
136}
137
138fn compound_move_for_group_candidate<S>(
139 group: &crate::builder::context::ScalarGroupContext<S>,
140 solution: &S,
141 candidate: crate::builder::context::ScalarGroupCandidate,
142) -> Option<crate::heuristic::r#move::CompoundScalarMove<S>>
143where
144 S: PlanningSolution + 'static,
145{
146 let mut edits = Vec::with_capacity(candidate.edits.len());
147 for edit in candidate.edits {
148 let member = group.member_for_edit(&edit)?;
149 if !member.value_is_legal(solution, edit.entity_index, edit.to_value) {
150 return None;
151 }
152 edits.push(crate::heuristic::r#move::CompoundScalarEdit {
153 descriptor_index: member.descriptor_index,
154 entity_index: edit.entity_index,
155 variable_index: member.variable_index,
156 variable_name: member.variable_name,
157 to_value: edit.to_value,
158 getter: member.getter,
159 setter: member.setter,
160 value_is_legal: None,
161 });
162 }
163
164 Some(crate::heuristic::r#move::CompoundScalarMove::with_label(
165 candidate.reason,
166 crate::heuristic::r#move::COMPOUND_SCALAR_VARIABLE,
167 edits,
168 ))
169}