Skip to main content

solverforge_solver/builder/selector/
grouped_scalar.rs

1pub 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}