solverforge_solver/phase/partitioned/
mod.rs1mod partitioner;
24
25use std::fmt::Debug;
26use std::sync::{Arc, Mutex};
27use std::thread;
28
29use solverforge_core::domain::PlanningSolution;
30use solverforge_scoring::ScoreDirector;
31
32use crate::phase::Phase;
33use crate::scope::SolverScope;
34
35pub use partitioner::{FunctionalPartitioner, SolutionPartitioner, ThreadCount};
36
37#[derive(Debug, Clone)]
39pub struct PartitionedSearchConfig {
40 pub thread_count: ThreadCount,
42 pub log_progress: bool,
44}
45
46impl Default for PartitionedSearchConfig {
47 fn default() -> Self {
48 Self {
49 thread_count: ThreadCount::Auto,
50 log_progress: false,
51 }
52 }
53}
54
55pub type ScoreDirectorFactory<S> = Arc<dyn Fn(S) -> Box<dyn ScoreDirector<S>> + Send + Sync>;
57
58pub type PhaseFactory<S> = Arc<dyn Fn() -> Vec<Box<dyn Phase<S>>> + Send + Sync>;
60
61pub struct PartitionedSearchPhase<S: PlanningSolution> {
71 partitioner: Box<dyn SolutionPartitioner<S>>,
73
74 score_director_factory: ScoreDirectorFactory<S>,
76
77 phase_factory: PhaseFactory<S>,
79
80 config: PartitionedSearchConfig,
82}
83
84impl<S: PlanningSolution> PartitionedSearchPhase<S> {
85 pub fn new(
87 partitioner: Box<dyn SolutionPartitioner<S>>,
88 score_director_factory: ScoreDirectorFactory<S>,
89 phase_factory: PhaseFactory<S>,
90 ) -> Self {
91 Self {
92 partitioner,
93 score_director_factory,
94 phase_factory,
95 config: PartitionedSearchConfig::default(),
96 }
97 }
98
99 pub fn with_config(
101 partitioner: Box<dyn SolutionPartitioner<S>>,
102 score_director_factory: ScoreDirectorFactory<S>,
103 phase_factory: PhaseFactory<S>,
104 config: PartitionedSearchConfig,
105 ) -> Self {
106 Self {
107 partitioner,
108 score_director_factory,
109 phase_factory,
110 config,
111 }
112 }
113
114 fn solve_partition(
116 partition: S,
117 score_director_factory: &ScoreDirectorFactory<S>,
118 phase_factory: &PhaseFactory<S>,
119 ) -> S {
120 let director = (score_director_factory)(partition);
122
123 let mut solver_scope = SolverScope::new(director);
125
126 let mut phases = (phase_factory)();
128 for phase in phases.iter_mut() {
129 phase.solve(&mut solver_scope);
130 }
131
132 solver_scope.take_best_or_working_solution()
134 }
135}
136
137impl<S: PlanningSolution> Debug for PartitionedSearchPhase<S> {
138 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139 f.debug_struct("PartitionedSearchPhase")
140 .field("partitioner", &self.partitioner)
141 .field("config", &self.config)
142 .finish()
143 }
144}
145
146impl<S: PlanningSolution + 'static> Phase<S> for PartitionedSearchPhase<S> {
147 fn solve(&mut self, solver_scope: &mut SolverScope<S>) {
148 let solution = solver_scope.score_director().working_solution().clone();
150
151 let partitions = self.partitioner.partition(&solution);
153 let partition_count = partitions.len();
154
155 if partition_count == 0 {
156 return;
157 }
158
159 let thread_count = self.config.thread_count.resolve(partition_count);
161
162 if self.config.log_progress {
163 println!(
164 "[PartitionedSearch] Solving {} partitions with {} threads",
165 partition_count, thread_count
166 );
167 }
168
169 let score_director_factory = Arc::clone(&self.score_director_factory);
171 let phase_factory = Arc::clone(&self.phase_factory);
172
173 let solved_partitions: Vec<S> = if thread_count == 1 || partition_count == 1 {
175 partitions
177 .into_iter()
178 .map(|p| Self::solve_partition(p, &score_director_factory, &phase_factory))
179 .collect()
180 } else {
181 let results: Arc<Mutex<Vec<Option<S>>>> =
183 Arc::new(Mutex::new(vec![None; partition_count]));
184
185 thread::scope(|s| {
186 for (i, partition) in partitions.into_iter().enumerate() {
187 let results = Arc::clone(&results);
188 let sdf = Arc::clone(&score_director_factory);
189 let pf = Arc::clone(&phase_factory);
190
191 s.spawn(move || {
192 let solved = Self::solve_partition(partition, &sdf, &pf);
193 let mut r = results.lock().unwrap();
194 r[i] = Some(solved);
195 });
196 }
197 });
198
199 let results = Arc::try_unwrap(results)
201 .unwrap_or_else(|_| panic!("All threads should be done"))
202 .into_inner()
203 .unwrap();
204
205 results.into_iter().map(|opt| opt.unwrap()).collect()
206 };
207
208 let merged = self.partitioner.merge(&solution, solved_partitions);
210
211 let director = solver_scope.score_director_mut();
214
215 let working = director.working_solution_mut();
217 *working = merged;
218
219 solver_scope.calculate_score();
221
222 solver_scope.update_best_solution();
224
225 if self.config.log_progress {
226 if let Some(score) = solver_scope.best_score() {
227 println!(
228 "[PartitionedSearch] Completed with merged score: {:?}",
229 score
230 );
231 }
232 }
233 }
234
235 fn phase_type_name(&self) -> &'static str {
236 "PartitionedSearch"
237 }
238}
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243 use solverforge_core::score::SimpleScore;
244
245 #[derive(Clone, Debug)]
246 struct TestSolution {
247 values: Vec<i32>,
248 score: Option<SimpleScore>,
249 }
250
251 impl PlanningSolution for TestSolution {
252 type Score = SimpleScore;
253
254 fn score(&self) -> Option<Self::Score> {
255 self.score
256 }
257
258 fn set_score(&mut self, score: Option<Self::Score>) {
259 self.score = score;
260 }
261 }
262
263 #[test]
264 fn test_config_default() {
265 let config = PartitionedSearchConfig::default();
266 assert_eq!(config.thread_count, ThreadCount::Auto);
267 assert!(!config.log_progress);
268 }
269
270 #[test]
271 fn test_phase_type_name() {
272 let test_solution = TestSolution {
273 values: vec![1, 2, 3],
274 score: None,
275 };
276 assert_eq!(test_solution.values.len(), 3);
277
278 let partitioner = Box::new(FunctionalPartitioner::new(
279 |s: &TestSolution| vec![s.clone()],
280 |_, partitions| partitions.into_iter().next().unwrap(),
281 ));
282
283 let sdf: ScoreDirectorFactory<TestSolution> = Arc::new(|_s| {
284 panic!("Should not be called in this test");
285 });
286
287 let pf: PhaseFactory<TestSolution> = Arc::new(Vec::new);
288
289 let phase = PartitionedSearchPhase::new(partitioner, sdf, pf);
290 assert_eq!(phase.phase_type_name(), "PartitionedSearch");
291 }
292
293 #[test]
294 fn test_phase_debug() {
295 let partitioner = Box::new(FunctionalPartitioner::new(
296 |s: &TestSolution| vec![s.clone()],
297 |_, partitions| partitions.into_iter().next().unwrap(),
298 ));
299
300 let sdf: ScoreDirectorFactory<TestSolution> = Arc::new(|_s| {
301 panic!("Should not be called in this test");
302 });
303
304 let pf: PhaseFactory<TestSolution> = Arc::new(Vec::new);
305
306 let phase = PartitionedSearchPhase::new(partitioner, sdf, pf);
307 let debug = format!("{:?}", phase);
308
309 assert!(debug.contains("PartitionedSearchPhase"));
310 assert!(debug.contains("FunctionalPartitioner"));
311 }
312}