1use std::fmt::Debug;
2use std::hash::Hash;
3
4use solverforge_core::domain::{PlanningSolution, SolutionDescriptor};
5use solverforge_core::score::{ParseableScore, Score};
6
7use crate::heuristic::r#move::Move;
8use crate::heuristic::selector::nearby_list_change::CrossEntityDistanceMeter;
9use crate::heuristic::MoveSelector;
10use crate::phase::localsearch::SelectorCursorSource;
11use crate::phase::localsearch::{Acceptor, LocalSearchForager, LocalSearchPhase};
12use crate::RuntimeBuildResult;
13
14use super::RuntimeModel;
15
16mod custom;
17
18pub use custom::{
19 CustomPhaseNode, CustomSearchPhase, NoDynamicExtensions, NoRuntimeExtensionPhase,
20 NoTypedExtensions, PartitionedPhaseNode, RuntimeExtensionPolicy, RuntimeExtensionRegistry,
21};
22
23pub struct SearchContext<
24 S,
25 V = usize,
26 DM = crate::heuristic::selector::DefaultCrossEntityDistanceMeter,
27 IDM = crate::heuristic::selector::DefaultCrossEntityDistanceMeter,
28> where
29 S: PlanningSolution,
30{
31 descriptor: SolutionDescriptor,
32 model: RuntimeModel<S, V, DM, IDM>,
33 random_seed: Option<u64>,
34}
35
36impl<S, V, DM, IDM> SearchContext<S, V, DM, IDM>
37where
38 S: PlanningSolution,
39{
40 pub fn new(
41 descriptor: SolutionDescriptor,
42 model: RuntimeModel<S, V, DM, IDM>,
43 random_seed: Option<u64>,
44 ) -> Self {
45 Self::try_new(descriptor, model, random_seed).unwrap_or_else(|error| match error {
46 crate::RuntimeBuildError::Declaration { message } => panic!("{message}"),
47 other => panic!("{other}"),
48 })
49 }
50
51 pub fn try_new(
52 descriptor: SolutionDescriptor,
53 model: RuntimeModel<S, V, DM, IDM>,
54 random_seed: Option<u64>,
55 ) -> RuntimeBuildResult<Self> {
56 let model = model
57 .resolve_dynamic_descriptor_indexes(&descriptor)
58 .map_err(crate::RuntimeBuildError::declaration)?;
59 Ok(Self {
60 descriptor,
61 model,
62 random_seed,
63 })
64 }
65
66 pub fn descriptor(&self) -> &SolutionDescriptor {
67 &self.descriptor
68 }
69
70 pub fn model(&self) -> &RuntimeModel<S, V, DM, IDM> {
71 &self.model
72 }
73
74 pub fn seed(&self) -> Option<u64> {
75 self.random_seed
76 }
77
78 pub fn defaults(self) -> SearchBuilder<S, V, DM, IDM, NoTypedExtensions> {
79 SearchBuilder {
80 context: self,
81 extensions: NoTypedExtensions,
82 }
83 }
84}
85
86pub trait Search<
92 S,
93 V = usize,
94 DM = crate::heuristic::selector::DefaultCrossEntityDistanceMeter,
95 IDM = crate::heuristic::selector::DefaultCrossEntityDistanceMeter,
96> where
97 S: PlanningSolution + 'static,
98 S::Score: Score + ParseableScore,
99 V: Clone + Copy + PartialEq + Eq + Hash + Into<usize> + Send + Sync + Debug + 'static,
100 DM: CrossEntityDistanceMeter<S> + Clone + Debug + Send + 'static,
101 IDM: CrossEntityDistanceMeter<S> + Clone + Debug + Send + 'static,
102{
103 type Extensions: RuntimeExtensionRegistry<S, V, DM, IDM>;
105
106 #[doc(hidden)]
112 fn into_runtime_parts(self) -> (SearchContext<S, V, DM, IDM>, Self::Extensions);
113}
114
115pub struct SearchBuilder<S, V, DM, IDM, Extensions>
116where
117 S: PlanningSolution,
118{
119 context: SearchContext<S, V, DM, IDM>,
120 extensions: Extensions,
121}
122
123impl<S, V, DM, IDM, Extensions> SearchBuilder<S, V, DM, IDM, Extensions>
124where
125 S: PlanningSolution,
126{
127 pub fn into_runtime_parts(self) -> (SearchContext<S, V, DM, IDM>, Extensions) {
129 (self.context, self.extensions)
130 }
131}
132
133impl<S, V, DM, IDM, Extensions> SearchBuilder<S, V, DM, IDM, Extensions>
134where
135 S: PlanningSolution + 'static,
136 S::Score: Score + ParseableScore,
137 V: Clone + Copy + PartialEq + Eq + Hash + Into<usize> + Send + Sync + Debug + 'static,
138 DM: CrossEntityDistanceMeter<S> + Clone + Debug + Send + 'static,
139 IDM: CrossEntityDistanceMeter<S> + Clone + Debug + Send + 'static,
140 Extensions: RuntimeExtensionRegistry<S, V, DM, IDM>,
141{
142 pub fn phase<P, F>(
143 self,
144 name: &'static str,
145 builder: F,
146 ) -> SearchBuilder<S, V, DM, IDM, CustomPhaseNode<Extensions, F, P>>
147 where
148 F: Fn(&SearchContext<S, V, DM, IDM>) -> P + Send + Sync + 'static,
149 P: CustomSearchPhase<S> + 'static,
150 {
151 assert!(
152 !self.extensions.contains_custom(name) && !self.extensions.contains_partitioned(name),
153 "custom phase `{name}` was registered more than once",
154 );
155 SearchBuilder {
156 context: self.context,
157 extensions: CustomPhaseNode::new(self.extensions, name, builder),
158 }
159 }
160
161 pub fn partitioned_phase<P, F>(
162 self,
163 name: &'static str,
164 builder: F,
165 ) -> SearchBuilder<S, V, DM, IDM, PartitionedPhaseNode<Extensions, F, P>>
166 where
167 F: Fn(&SearchContext<S, V, DM, IDM>, &solverforge_config::PartitionedSearchConfig) -> P
168 + Send
169 + Sync
170 + 'static,
171 P: CustomSearchPhase<S> + 'static,
172 {
173 assert!(
174 !self.extensions.contains_custom(name) && !self.extensions.contains_partitioned(name),
175 "partitioned_search partitioner `{name}` was registered more than once",
176 );
177 SearchBuilder {
178 context: self.context,
179 extensions: PartitionedPhaseNode::new(self.extensions, name, builder),
180 }
181 }
182}
183
184impl<S, V, DM, IDM, Extensions> Search<S, V, DM, IDM> for SearchBuilder<S, V, DM, IDM, Extensions>
185where
186 S: PlanningSolution + 'static,
187 S::Score: Score + ParseableScore,
188 V: Clone + Copy + PartialEq + Eq + Hash + Into<usize> + Send + Sync + Debug + 'static,
189 DM: CrossEntityDistanceMeter<S> + Clone + Debug + Send + 'static,
190 IDM: CrossEntityDistanceMeter<S> + Clone + Debug + Send + 'static,
191 Extensions: RuntimeExtensionRegistry<S, V, DM, IDM>,
192{
193 type Extensions = Extensions;
194
195 fn into_runtime_parts(self) -> (SearchContext<S, V, DM, IDM>, Self::Extensions) {
196 SearchBuilder::into_runtime_parts(self)
197 }
198}
199
200pub fn local_search<S, M, MS, A, Fo>(
201 move_selector: MS,
202 acceptor: A,
203 forager: Fo,
204) -> LocalSearchPhase<S, M, SelectorCursorSource<MS>, A, Fo>
205where
206 S: PlanningSolution,
207 M: Move<S> + 'static,
208 MS: MoveSelector<S, M>,
209 A: Acceptor<S>,
210 Fo: LocalSearchForager<S, M>,
211{
212 LocalSearchPhase::new(move_selector, acceptor, forager, None)
213}
214
215#[cfg(test)]
216mod tests;