Skip to main content

solverforge_solver/heuristic/move/
list_ruin.rs

1//! Public static facade for canonical list ruin-and-recreate mechanics.
2
3use std::borrow::Cow;
4use std::fmt::Debug;
5
6use smallvec::SmallVec;
7use solverforge_core::domain::PlanningSolution;
8use solverforge_scoring::Director;
9
10use super::list_kernel::{
11    merged_ruin_sources, ruin_count, ruin_do_move, ruin_entity_indices, ruin_is_doable,
12    ruin_tabu_signature, ruin_undo_move, single_ruin_source, RuinSources, RuinUndo,
13    StaticListRuinAccess,
14};
15use super::{Move, MoveTabuSignature};
16
17/// A self-contained list ruin-and-recreate move.
18///
19/// Its public function-pointer constructor remains unchanged. Candidate
20/// mutation, restore, undo, owner policy, precedence filtering, and tabu
21/// mechanics live in `move::list_kernel` so runtime list moves reuse the same
22/// implementation instead of translating through this facade.
23pub struct ListRuinMove<S, V> {
24    entity_index: usize,
25    element_indices: SmallVec<[usize; 8]>,
26    sources: RuinSources,
27    entity_indices: SmallVec<[usize; 8]>,
28    access: StaticListRuinAccess<S, V>,
29    skip_empty_destinations: bool,
30}
31
32impl<S, V: Debug> Debug for ListRuinMove<S, V> {
33    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34        formatter
35            .debug_struct("ListRuinMove")
36            .field("sources", &self.sources)
37            .field("variable_name", &self.access.variable_name)
38            .finish()
39    }
40}
41
42impl<S, V> ListRuinMove<S, V> {
43    #[allow(clippy::too_many_arguments)]
44    pub fn new(
45        entity_index: usize,
46        element_indices: &[usize],
47        entity_count: fn(&S) -> usize,
48        list_len: fn(&S, usize) -> usize,
49        list_get: fn(&S, usize, usize) -> Option<V>,
50        list_remove: fn(&mut S, usize, usize) -> V,
51        list_insert: fn(&mut S, usize, usize, V),
52        variable_name: &'static str,
53        descriptor_index: usize,
54    ) -> Self {
55        Self::from_sources(
56            single_ruin_source(entity_index, element_indices),
57            entity_count,
58            list_len,
59            list_get,
60            list_remove,
61            list_insert,
62            variable_name,
63            descriptor_index,
64        )
65    }
66
67    #[allow(clippy::too_many_arguments)]
68    pub(crate) fn new_multi_source(
69        sources: &[(usize, SmallVec<[usize; 8]>)],
70        entity_count: fn(&S) -> usize,
71        list_len: fn(&S, usize) -> usize,
72        list_get: fn(&S, usize, usize) -> Option<V>,
73        list_remove: fn(&mut S, usize, usize) -> V,
74        list_insert: fn(&mut S, usize, usize, V),
75        variable_name: &'static str,
76        descriptor_index: usize,
77    ) -> Self {
78        Self::from_sources(
79            merged_ruin_sources(sources),
80            entity_count,
81            list_len,
82            list_get,
83            list_remove,
84            list_insert,
85            variable_name,
86            descriptor_index,
87        )
88    }
89
90    #[allow(clippy::too_many_arguments)]
91    fn from_sources(
92        sources: RuinSources,
93        entity_count: fn(&S) -> usize,
94        list_len: fn(&S, usize) -> usize,
95        list_get: fn(&S, usize, usize) -> Option<V>,
96        list_remove: fn(&mut S, usize, usize) -> V,
97        list_insert: fn(&mut S, usize, usize, V),
98        variable_name: &'static str,
99        descriptor_index: usize,
100    ) -> Self {
101        let (entity_index, element_indices) = sources
102            .first()
103            .cloned()
104            .unwrap_or_else(|| (0, SmallVec::new()));
105        let entity_indices = ruin_entity_indices(&sources);
106        Self {
107            entity_index,
108            element_indices,
109            sources,
110            entity_indices,
111            access: StaticListRuinAccess {
112                entity_count,
113                list_len,
114                list_get,
115                list_remove,
116                list_insert,
117                element_owner_fn: None,
118                precedence_element_count: None,
119                precedence_index_to_element: None,
120                precedence_successors_fn: None,
121                variable_name,
122                descriptor_index,
123            },
124            skip_empty_destinations: false,
125        }
126    }
127
128    pub fn with_element_owner_fn(
129        mut self,
130        element_owner_fn: Option<fn(&S, &V) -> Option<usize>>,
131    ) -> Self {
132        self.access.element_owner_fn = element_owner_fn;
133        self
134    }
135
136    pub(crate) fn with_precedence_hooks(
137        mut self,
138        element_count: Option<fn(&S) -> usize>,
139        index_to_element: Option<fn(&S, usize) -> V>,
140        successors_fn: Option<fn(&S, V, &mut Vec<V>)>,
141    ) -> Self {
142        self.access.precedence_element_count = element_count;
143        self.access.precedence_index_to_element = index_to_element;
144        self.access.precedence_successors_fn = successors_fn;
145        self
146    }
147
148    pub fn with_skip_empty_destinations(mut self, skip_empty_destinations: bool) -> Self {
149        self.skip_empty_destinations = skip_empty_destinations;
150        self
151    }
152
153    pub fn entity_index(&self) -> usize {
154        self.entity_index
155    }
156
157    pub fn element_indices(&self) -> &[usize] {
158        &self.element_indices
159    }
160
161    pub fn ruin_count(&self) -> usize {
162        ruin_count(&self.sources)
163    }
164}
165
166impl<S, V> Move<S> for ListRuinMove<S, V>
167where
168    S: PlanningSolution,
169    V: Clone + PartialEq + Send + Sync + Debug + 'static,
170{
171    type Undo = RuinUndo;
172
173    fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
174        ruin_is_doable(&self.access, &self.sources, score_director)
175    }
176
177    fn do_move<D: Director<S>>(&self, score_director: &mut D) -> Self::Undo {
178        ruin_do_move(
179            &self.access,
180            &self.sources,
181            self.skip_empty_destinations,
182            score_director,
183        )
184    }
185
186    fn undo_move<D: Director<S>>(&self, score_director: &mut D, undo: Self::Undo) {
187        ruin_undo_move(&self.access, &self.sources, undo, score_director);
188    }
189
190    fn descriptor_index(&self) -> usize {
191        self.access.descriptor_index
192    }
193
194    fn entity_indices(&self) -> &[usize] {
195        &self.entity_indices
196    }
197
198    fn variable_name(&self) -> &str {
199        self.access.variable_name
200    }
201
202    fn telemetry_label(&self) -> &'static str {
203        "list_ruin"
204    }
205
206    fn tabu_signature<D: Director<S>>(&self, score_director: &D) -> Cow<'_, MoveTabuSignature> {
207        Cow::Owned(ruin_tabu_signature(
208            &self.access,
209            &self.sources,
210            &self.entity_indices,
211            score_director,
212        ))
213    }
214}
215
216#[cfg(test)]
217pub(crate) fn final_positions_after_insertions(
218    placements: &SmallVec<[(usize, usize); 8]>,
219) -> SmallVec<[usize; 8]> {
220    super::list_kernel::final_positions_after_insertions(placements)
221}