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