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    RuinValueTransfer, 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> Clone for ListRuinMove<S, V> {
32    fn clone(&self) -> Self {
33        Self {
34            entity_index: self.entity_index,
35            element_indices: self.element_indices.clone(),
36            sources: self.sources.clone(),
37            entity_indices: self.entity_indices.clone(),
38            access: self.access,
39            skip_empty_destinations: self.skip_empty_destinations,
40        }
41    }
42}
43
44impl<S, V: Debug> Debug for ListRuinMove<S, V> {
45    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46        formatter
47            .debug_struct("ListRuinMove")
48            .field("sources", &self.sources)
49            .field("variable_name", &self.access.variable_name)
50            .finish()
51    }
52}
53
54impl<S, V> ListRuinMove<S, V> {
55    #[allow(clippy::too_many_arguments)]
56    pub fn new(
57        entity_index: usize,
58        element_indices: &[usize],
59        entity_count: fn(&S) -> usize,
60        list_len: fn(&S, usize) -> usize,
61        list_get: fn(&S, usize, usize) -> Option<V>,
62        list_remove: fn(&mut S, usize, usize) -> V,
63        list_insert: fn(&mut S, usize, usize, V),
64        variable_name: &'static str,
65        descriptor_index: usize,
66    ) -> Self {
67        Self::from_sources(
68            single_ruin_source(entity_index, element_indices),
69            entity_count,
70            list_len,
71            list_get,
72            list_remove,
73            list_insert,
74            variable_name,
75            descriptor_index,
76        )
77    }
78
79    #[allow(clippy::too_many_arguments)]
80    pub(crate) fn new_multi_source(
81        sources: &[(usize, SmallVec<[usize; 8]>)],
82        entity_count: fn(&S) -> usize,
83        list_len: fn(&S, usize) -> usize,
84        list_get: fn(&S, usize, usize) -> Option<V>,
85        list_remove: fn(&mut S, usize, usize) -> V,
86        list_insert: fn(&mut S, usize, usize, V),
87        variable_name: &'static str,
88        descriptor_index: usize,
89    ) -> Self {
90        Self::from_sources(
91            merged_ruin_sources(sources),
92            entity_count,
93            list_len,
94            list_get,
95            list_remove,
96            list_insert,
97            variable_name,
98            descriptor_index,
99        )
100    }
101
102    #[allow(clippy::too_many_arguments)]
103    fn from_sources(
104        sources: RuinSources,
105        entity_count: fn(&S) -> usize,
106        list_len: fn(&S, usize) -> usize,
107        list_get: fn(&S, usize, usize) -> Option<V>,
108        list_remove: fn(&mut S, usize, usize) -> V,
109        list_insert: fn(&mut S, usize, usize, V),
110        variable_name: &'static str,
111        descriptor_index: usize,
112    ) -> Self {
113        let (entity_index, element_indices) = sources
114            .first()
115            .cloned()
116            .unwrap_or_else(|| (0, SmallVec::new()));
117        let entity_indices = ruin_entity_indices(&sources);
118        Self {
119            entity_index,
120            element_indices,
121            sources,
122            entity_indices,
123            access: StaticListRuinAccess {
124                entity_count,
125                list_len,
126                list_get,
127                list_remove,
128                list_insert,
129                element_owner_fn: None,
130                precedence_element_count: None,
131                precedence_index_to_element: None,
132                precedence_successors_fn: None,
133                variable_name,
134                descriptor_index,
135            },
136            skip_empty_destinations: false,
137        }
138    }
139
140    pub fn with_element_owner_fn(
141        mut self,
142        element_owner_fn: Option<fn(&S, &V) -> Option<usize>>,
143    ) -> Self {
144        self.access.element_owner_fn = element_owner_fn;
145        self
146    }
147
148    pub(crate) fn with_precedence_hooks(
149        mut self,
150        element_count: Option<fn(&S) -> usize>,
151        index_to_element: Option<fn(&S, usize) -> V>,
152        successors_fn: Option<fn(&S, V, &mut Vec<V>)>,
153    ) -> Self {
154        self.access.precedence_element_count = element_count;
155        self.access.precedence_index_to_element = index_to_element;
156        self.access.precedence_successors_fn = successors_fn;
157        self
158    }
159
160    pub fn with_skip_empty_destinations(mut self, skip_empty_destinations: bool) -> Self {
161        self.skip_empty_destinations = skip_empty_destinations;
162        self
163    }
164
165    pub fn entity_index(&self) -> usize {
166        self.entity_index
167    }
168
169    pub fn element_indices(&self) -> &[usize] {
170        &self.element_indices
171    }
172
173    pub fn ruin_count(&self) -> usize {
174        ruin_count(&self.sources)
175    }
176}
177
178impl<S, V> Move<S> for ListRuinMove<S, V>
179where
180    S: PlanningSolution,
181    V: Clone + PartialEq + Send + Sync + Debug + 'static,
182{
183    type Undo = RuinUndo;
184
185    fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool {
186        ruin_is_doable(&self.access, &self.sources, score_director)
187    }
188
189    fn do_move<D: Director<S>>(&self, score_director: &mut D) -> Self::Undo {
190        ruin_do_move(
191            &self.access,
192            &self.sources,
193            self.skip_empty_destinations,
194            RuinValueTransfer::CloneBeforeInsert,
195            score_director,
196        )
197    }
198
199    fn undo_move<D: Director<S>>(&self, score_director: &mut D, undo: Self::Undo) {
200        ruin_undo_move(&self.access, &self.sources, undo, score_director);
201    }
202
203    fn descriptor_index(&self) -> usize {
204        self.access.descriptor_index
205    }
206
207    fn entity_indices(&self) -> &[usize] {
208        &self.entity_indices
209    }
210
211    fn variable_name(&self) -> &str {
212        self.access.variable_name
213    }
214
215    fn telemetry_label(&self) -> &'static str {
216        "list_ruin"
217    }
218
219    fn tabu_signature<D: Director<S>>(&self, score_director: &D) -> MoveTabuSignature {
220        ruin_tabu_signature(
221            &self.access,
222            &self.sources,
223            &self.entity_indices,
224            score_director,
225        )
226    }
227}
228
229#[cfg(test)]
230pub(crate) fn final_positions_after_insertions(
231    placements: &SmallVec<[(usize, usize); 8]>,
232) -> SmallVec<[usize; 8]> {
233    super::list_kernel::final_positions_after_insertions(placements)
234}