solverforge_solver/heuristic/selector/
dynamic_scalar_change.rs1use std::fmt::{self, Debug};
2use std::marker::PhantomData;
3
4use solverforge_core::domain::{DynamicScalarVariableSlot, PlanningSolution};
5use solverforge_scoring::Director;
6
7use crate::heuristic::r#move::{DynamicScalarChangeMove, MoveArena};
8
9use super::move_selector::{
10 CandidateId, CandidateStore, MoveCandidateRef, MoveCursor, MoveSelector, MoveStreamContext,
11};
12
13pub struct DynamicScalarChangeMoveSelector<S> {
14 slot: DynamicScalarVariableSlot<S>,
15 value_candidate_limit: Option<usize>,
16}
17
18struct DynamicScalarEntityValues {
19 entity_index: usize,
20 values: Vec<usize>,
21 current_assigned: bool,
22}
23
24pub struct DynamicScalarChangeMoveCursor<S>
25where
26 S: PlanningSolution,
27{
28 store: CandidateStore<S, DynamicScalarChangeMove<S>>,
29 entity_values: Vec<DynamicScalarEntityValues>,
30 entity_offset: usize,
31 value_offset: usize,
32 slot: DynamicScalarVariableSlot<S>,
33 _phantom: PhantomData<fn() -> S>,
34}
35
36impl<S> DynamicScalarChangeMoveCursor<S>
37where
38 S: PlanningSolution,
39{
40 fn new(
41 slot: DynamicScalarVariableSlot<S>,
42 entity_values: Vec<DynamicScalarEntityValues>,
43 ) -> Self {
44 Self {
45 store: CandidateStore::new(),
46 entity_values,
47 entity_offset: 0,
48 value_offset: 0,
49 slot,
50 _phantom: PhantomData,
51 }
52 }
53}
54
55impl<S> MoveCursor<S, DynamicScalarChangeMove<S>> for DynamicScalarChangeMoveCursor<S>
56where
57 S: PlanningSolution,
58{
59 fn next_candidate(&mut self) -> Option<CandidateId> {
60 while self.entity_offset < self.entity_values.len() {
61 let entity_values = &self.entity_values[self.entity_offset];
62 if self.value_offset < entity_values.values.len() {
63 let value = entity_values.values[self.value_offset];
64 self.value_offset += 1;
65 return Some(self.store.push(DynamicScalarChangeMove::new(
66 self.slot.clone(),
67 entity_values.entity_index,
68 Some(value),
69 )));
70 }
71
72 let to_none_offset = entity_values.values.len();
73 if self.slot.allows_unassigned
74 && entity_values.current_assigned
75 && self.value_offset == to_none_offset
76 {
77 self.value_offset += 1;
78 return Some(self.store.push(DynamicScalarChangeMove::new(
79 self.slot.clone(),
80 entity_values.entity_index,
81 None,
82 )));
83 }
84
85 self.entity_offset += 1;
86 self.value_offset = 0;
87 }
88
89 None
90 }
91
92 fn candidate(
93 &self,
94 id: CandidateId,
95 ) -> Option<MoveCandidateRef<'_, S, DynamicScalarChangeMove<S>>> {
96 self.store.candidate(id)
97 }
98
99 fn take_candidate(&mut self, id: CandidateId) -> DynamicScalarChangeMove<S> {
100 self.store.take_candidate(id)
101 }
102}
103
104impl<S> Iterator for DynamicScalarChangeMoveCursor<S>
105where
106 S: PlanningSolution,
107{
108 type Item = DynamicScalarChangeMove<S>;
109
110 fn next(&mut self) -> Option<Self::Item> {
111 let id = self.next_candidate()?;
112 Some(self.take_candidate(id))
113 }
114}
115
116impl<S> DynamicScalarChangeMoveSelector<S> {
117 pub fn new(slot: DynamicScalarVariableSlot<S>, value_candidate_limit: Option<usize>) -> Self {
118 Self {
119 slot,
120 value_candidate_limit,
121 }
122 }
123}
124
125impl<S> Debug for DynamicScalarChangeMoveSelector<S> {
126 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127 f.debug_struct("DynamicScalarChangeMoveSelector")
128 .field("slot", &self.slot)
129 .field("value_candidate_limit", &self.value_candidate_limit)
130 .finish()
131 }
132}
133
134impl<S> MoveSelector<S, DynamicScalarChangeMove<S>> for DynamicScalarChangeMoveSelector<S>
135where
136 S: PlanningSolution,
137{
138 type Cursor<'a>
139 = DynamicScalarChangeMoveCursor<S>
140 where
141 Self: 'a;
142
143 fn open_cursor<'a, D: Director<S>>(&'a self, score_director: &D) -> Self::Cursor<'a> {
144 self.open_cursor_with_context(score_director, MoveStreamContext::default())
145 }
146
147 fn open_cursor_with_context<'a, D: Director<S>>(
148 &'a self,
149 score_director: &D,
150 context: MoveStreamContext,
151 ) -> Self::Cursor<'a> {
152 let solution = score_director.working_solution();
153 let value_limit = self.value_candidate_limit.unwrap_or(usize::MAX);
154 let mut entity_values = (0..self.slot.entity_count(solution))
155 .map(|entity_index| {
156 let current_assigned = self.slot.current_value(solution, entity_index).is_some();
157 let mut values = self
158 .slot
159 .candidate_values(solution, entity_index)
160 .iter()
161 .copied()
162 .take(value_limit)
163 .collect::<Vec<_>>();
164 let start = context.start_offset(
165 values.len(),
166 0xD94E_5CA1_0000_0000
167 ^ entity_index as u64
168 ^ ((self.slot.descriptor_index() as u64) << 32)
169 ^ self.slot.variable.0 as u64,
170 );
171 values.rotate_left(start);
172 DynamicScalarEntityValues {
173 entity_index,
174 values,
175 current_assigned,
176 }
177 })
178 .collect::<Vec<_>>();
179 let entity_start = context.start_offset(
180 entity_values.len(),
181 0xD94E_5CA1_0000_0001
182 ^ ((self.slot.descriptor_index() as u64) << 32)
183 ^ self.slot.variable.0 as u64,
184 );
185 entity_values.rotate_left(entity_start);
186 DynamicScalarChangeMoveCursor::new(self.slot.clone(), entity_values)
187 }
188
189 fn size<D: Director<S>>(&self, score_director: &D) -> usize {
190 let solution = score_director.working_solution();
191 let value_limit = self.value_candidate_limit.unwrap_or(usize::MAX);
192 (0..self.slot.entity_count(solution))
193 .map(|entity_index| {
194 let candidate_count = self
195 .slot
196 .candidate_values(solution, entity_index)
197 .iter()
198 .take(value_limit)
199 .count();
200 let unassigned_count = usize::from(
201 self.slot.allows_unassigned
202 && self.slot.current_value(solution, entity_index).is_some(),
203 );
204 candidate_count + unassigned_count
205 })
206 .sum()
207 }
208
209 fn append_moves<D: Director<S>>(
210 &self,
211 score_director: &D,
212 arena: &mut MoveArena<DynamicScalarChangeMove<S>>,
213 ) {
214 let mut cursor = self.open_cursor(score_director);
215 while let Some(id) = cursor.next_candidate() {
216 arena.push(cursor.take_candidate(id));
217 }
218 }
219}