solverforge_solver/phase/partitioned/
phase.rs1use std::fmt::Debug;
4use std::marker::PhantomData;
5
6use rayon::prelude::*;
7use solverforge_core::domain::PlanningSolution;
8use solverforge_scoring::Director;
9
10use crate::phase::Phase;
11use crate::scope::BestSolutionCallback;
12use crate::scope::SolverScope;
13
14use super::child_phases::ChildPhases;
15use super::config::PartitionedSearchConfig;
16use super::partitioner::SolutionPartitioner;
17
18pub struct PartitionedSearchPhase<S, D, PD, Part, SDF, PF, CP>
38where
39 S: PlanningSolution,
40 D: Director<S>,
41 PD: Director<S>,
42 Part: SolutionPartitioner<S>,
43 SDF: Fn(S) -> PD + Send + Sync,
44 PF: Fn() -> CP + Send + Sync,
45 CP: ChildPhases<S, PD>,
46{
47 partitioner: Part,
49
50 score_director_factory: SDF,
52
53 phase_factory: PF,
55
56 config: PartitionedSearchConfig,
58
59 _marker: PhantomData<fn(S, D, PD, CP)>,
60}
61
62impl<S, D, PD, Part, SDF, PF, CP> PartitionedSearchPhase<S, D, PD, Part, SDF, PF, CP>
63where
64 S: PlanningSolution,
65 D: Director<S>,
66 PD: Director<S>,
67 Part: SolutionPartitioner<S>,
68 SDF: Fn(S) -> PD + Send + Sync,
69 PF: Fn() -> CP + Send + Sync,
70 CP: ChildPhases<S, PD>,
71{
72 pub fn new(partitioner: Part, score_director_factory: SDF, phase_factory: PF) -> Self {
74 Self {
75 partitioner,
76 score_director_factory,
77 phase_factory,
78 config: PartitionedSearchConfig::default(),
79 _marker: PhantomData,
80 }
81 }
82
83 pub fn with_config(
85 partitioner: Part,
86 score_director_factory: SDF,
87 phase_factory: PF,
88 config: PartitionedSearchConfig,
89 ) -> Self {
90 Self {
91 partitioner,
92 score_director_factory,
93 phase_factory,
94 config,
95 _marker: PhantomData,
96 }
97 }
98}
99
100impl<S, D, PD, Part, SDF, PF, CP> Debug for PartitionedSearchPhase<S, D, PD, Part, SDF, PF, CP>
101where
102 S: PlanningSolution,
103 D: Director<S>,
104 PD: Director<S>,
105 Part: SolutionPartitioner<S> + Debug,
106 SDF: Fn(S) -> PD + Send + Sync,
107 PF: Fn() -> CP + Send + Sync,
108 CP: ChildPhases<S, PD>,
109{
110 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111 f.debug_struct("PartitionedSearchPhase")
112 .field("partitioner", &self.partitioner)
113 .field("config", &self.config)
114 .finish()
115 }
116}
117
118impl<S, D, BestCb, PD, Part, SDF, PF, CP> Phase<S, D, BestCb>
119 for PartitionedSearchPhase<S, D, PD, Part, SDF, PF, CP>
120where
121 S: PlanningSolution + 'static,
122 D: Director<S>,
123 BestCb: BestSolutionCallback<S>,
124 PD: Director<S> + 'static,
125 Part: SolutionPartitioner<S>,
126 SDF: Fn(S) -> PD + Send + Sync,
127 PF: Fn() -> CP + Send + Sync,
128 CP: ChildPhases<S, PD> + Send,
129{
130 fn solve(&mut self, solver_scope: &mut SolverScope<S, D, BestCb>) {
131 let solution = solver_scope.score_director().working_solution().clone();
133
134 let partitions = self.partitioner.partition(&solution);
136 let partition_count = partitions.len();
137
138 if partition_count == 0 {
139 return;
140 }
141
142 let thread_count = self.config.thread_count.resolve(partition_count);
144
145 if self.config.log_progress {
146 tracing::info!(event = "phase_start", phase = "PartitionedSearch",);
147 }
148
149 let solved_partitions: Vec<S> = if thread_count == 1 || partition_count == 1 {
151 partitions
152 .into_iter()
153 .map(|p| self.solve_partition(p))
154 .collect()
155 } else {
156 partitions
157 .into_par_iter()
158 .map(|partition| {
159 let director = (self.score_director_factory)(partition);
160 let mut solver_scope = SolverScope::new(director);
161 let mut phases = (self.phase_factory)();
162 phases.solve_all(&mut solver_scope);
163 solver_scope.take_best_or_working_solution()
164 })
165 .collect()
166 };
167
168 let merged = self.partitioner.merge(&solution, solved_partitions);
170
171 let director = solver_scope.score_director_mut();
173
174 let working = director.working_solution_mut();
176 *working = merged;
177
178 solver_scope.calculate_score();
180
181 solver_scope.update_best_solution();
183
184 if self.config.log_progress {
185 if let Some(score) = solver_scope.best_score() {
186 tracing::info!(
187 event = "phase_end",
188 phase = "PartitionedSearch",
189 score = %format!("{:?}", score),
190 );
191 }
192 }
193 }
194
195 fn phase_type_name(&self) -> &'static str {
196 "PartitionedSearch"
197 }
198}
199
200impl<S, D, PD, Part, SDF, PF, CP> PartitionedSearchPhase<S, D, PD, Part, SDF, PF, CP>
201where
202 S: PlanningSolution,
203 D: Director<S>,
204 PD: Director<S>,
205 Part: SolutionPartitioner<S>,
206 SDF: Fn(S) -> PD + Send + Sync,
207 PF: Fn() -> CP + Send + Sync,
208 CP: ChildPhases<S, PD>,
209{
210 fn solve_partition(&self, partition: S) -> S {
212 let director = (self.score_director_factory)(partition);
214
215 let mut solver_scope = SolverScope::new(director);
217
218 let mut phases = (self.phase_factory)();
220 phases.solve_all(&mut solver_scope);
221
222 solver_scope.take_best_or_working_solution()
224 }
225}
226
227#[cfg(test)]
228mod tests {
229 use super::super::partitioner::ThreadCount;
230 use super::*;
231
232 #[test]
233 fn test_config_default() {
234 let config = PartitionedSearchConfig::default();
235 assert_eq!(config.thread_count, ThreadCount::Auto);
236 assert!(!config.log_progress);
237 }
238}