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 {
73 Self {
74 partitioner,
75 score_director_factory,
76 phase_factory,
77 config: PartitionedSearchConfig::default(),
78 _marker: PhantomData,
79 }
80 }
81
82 pub fn with_config(
83 partitioner: Part,
84 score_director_factory: SDF,
85 phase_factory: PF,
86 config: PartitionedSearchConfig,
87 ) -> Self {
88 Self {
89 partitioner,
90 score_director_factory,
91 phase_factory,
92 config,
93 _marker: PhantomData,
94 }
95 }
96}
97
98impl<S, D, PD, Part, SDF, PF, CP> Debug for PartitionedSearchPhase<S, D, PD, Part, SDF, PF, CP>
99where
100 S: PlanningSolution,
101 D: Director<S>,
102 PD: Director<S>,
103 Part: SolutionPartitioner<S> + Debug,
104 SDF: Fn(S) -> PD + Send + Sync,
105 PF: Fn() -> CP + Send + Sync,
106 CP: ChildPhases<S, PD>,
107{
108 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109 f.debug_struct("PartitionedSearchPhase")
110 .field("partitioner", &self.partitioner)
111 .field("config", &self.config)
112 .finish()
113 }
114}
115
116impl<S, D, BestCb, PD, Part, SDF, PF, CP> Phase<S, D, BestCb>
117 for PartitionedSearchPhase<S, D, PD, Part, SDF, PF, CP>
118where
119 S: PlanningSolution + 'static,
120 D: Director<S>,
121 BestCb: BestSolutionCallback<S>,
122 PD: Director<S> + 'static,
123 Part: SolutionPartitioner<S>,
124 SDF: Fn(S) -> PD + Send + Sync,
125 PF: Fn() -> CP + Send + Sync,
126 CP: ChildPhases<S, PD> + Send,
127{
128 fn solve(&mut self, solver_scope: &mut SolverScope<S, D, BestCb>) {
129 let solution = solver_scope.score_director().working_solution().clone();
131
132 let partitions = self.partitioner.partition(&solution);
134 let partition_count = partitions.len();
135
136 if partition_count == 0 {
137 return;
138 }
139
140 let thread_count = self.config.thread_count.resolve(partition_count);
142
143 if self.config.log_progress {
144 tracing::info!(event = "phase_start", phase = "PartitionedSearch",);
145 }
146
147 let solved_partitions: Vec<S> = if thread_count == 1 || partition_count == 1 {
149 partitions
150 .into_iter()
151 .map(|p| self.solve_partition(p))
152 .collect()
153 } else {
154 partitions
155 .into_par_iter()
156 .map(|partition| {
157 let director = (self.score_director_factory)(partition);
158 let mut solver_scope = SolverScope::new(director);
159 let mut phases = (self.phase_factory)();
160 phases.solve_all(&mut solver_scope);
161 solver_scope.take_best_or_working_solution()
162 })
163 .collect()
164 };
165
166 let merged = self.partitioner.merge(&solution, solved_partitions);
168
169 let director = solver_scope.score_director_mut();
171
172 let working = director.working_solution_mut();
174 *working = merged;
175
176 solver_scope.calculate_score();
178
179 solver_scope.update_best_solution();
181
182 if self.config.log_progress {
183 if let Some(score) = solver_scope.best_score() {
184 tracing::info!(
185 event = "phase_end",
186 phase = "PartitionedSearch",
187 score = %format!("{:?}", score),
188 );
189 }
190 }
191 }
192
193 fn phase_type_name(&self) -> &'static str {
194 "PartitionedSearch"
195 }
196}
197
198impl<S, D, PD, Part, SDF, PF, CP> PartitionedSearchPhase<S, D, PD, Part, SDF, PF, CP>
199where
200 S: PlanningSolution,
201 D: Director<S>,
202 PD: Director<S>,
203 Part: SolutionPartitioner<S>,
204 SDF: Fn(S) -> PD + Send + Sync,
205 PF: Fn() -> CP + Send + Sync,
206 CP: ChildPhases<S, PD>,
207{
208 fn solve_partition(&self, partition: S) -> S {
210 let director = (self.score_director_factory)(partition);
212
213 let mut solver_scope = SolverScope::new(director);
215
216 let mut phases = (self.phase_factory)();
218 phases.solve_all(&mut solver_scope);
219
220 solver_scope.take_best_or_working_solution()
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::super::partitioner::ThreadCount;
228 use super::*;
229
230 #[test]
231 fn test_config_default() {
232 let config = PartitionedSearchConfig::default();
233 assert_eq!(config.thread_count, ThreadCount::Auto);
234 assert!(!config.log_progress);
235 }
236}