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        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((
116                    edit.descriptor_index(),
117                    edit.entity_index(),
118                    edit.variable_name(),
119                ))
120            }) {
121                continue;
122            }
123
124            let Some(mov) = compound_move_for_group_candidate(&self.group, solution, candidate)
125            else {
126                continue;
127            };
128            let mov = mov.with_require_hard_improvement(self.require_hard_improvement);
129            if mov.is_doable(score_director) {
130                store.push(ScalarMoveUnion::CompoundScalar(mov));
131            }
132        }
133
134        GroupedScalarCursor::new(store)
135    }
136
137    fn size<D: solverforge_scoring::Director<S>>(&self, _score_director: &D) -> usize {
138        self.max_moves_per_step
139    }
140}
141
142fn compound_move_for_group_candidate<S>(
143    group: &crate::builder::context::ScalarGroupBinding<S>,
144    solution: &S,
145    candidate: crate::builder::context::ScalarCandidate<S>,
146) -> Option<crate::heuristic::r#move::CompoundScalarMove<S>>
147where
148    S: PlanningSolution + 'static,
149{
150    let reason = candidate.reason();
151    let mut edits = Vec::with_capacity(candidate.edits().len());
152    for edit in candidate.into_edits() {
153        let member = group.member_for_edit(&edit)?;
154        if !member.value_is_legal(solution, edit.entity_index(), edit.to_value()) {
155            return None;
156        }
157        edits.push(crate::heuristic::r#move::CompoundScalarEdit {
158            descriptor_index: member.descriptor_index,
159            entity_index: edit.entity_index(),
160            variable_index: member.variable_index,
161            variable_name: member.variable_name,
162            to_value: edit.to_value(),
163            getter: member.getter,
164            setter: member.setter,
165            value_is_legal: None,
166        });
167    }
168
169    Some(crate::heuristic::r#move::CompoundScalarMove::with_label(
170        reason,
171        crate::heuristic::r#move::COMPOUND_SCALAR_VARIABLE,
172        edits,
173    ))
174}