solverforge_solver/phase/partitioned/
partitioner.rs1use std::fmt::Debug;
7
8use solverforge_core::domain::PlanningSolution;
9
10pub trait SolutionPartitioner<S: PlanningSolution>: Send + Sync + Debug {
26 fn partition(&self, solution: &S) -> Vec<S>;
40
41 fn merge(&self, original: &S, partitions: Vec<S>) -> S;
55
56 fn recommended_partition_count(&self) -> Option<usize> {
61 None
62 }
63}
64
65pub struct FunctionalPartitioner<S, PF, MF>
70where
71 S: PlanningSolution,
72 PF: Fn(&S) -> Vec<S> + Send + Sync,
73 MF: Fn(&S, Vec<S>) -> S + Send + Sync,
74{
75 partition_fn: PF,
76 merge_fn: MF,
77 recommended_count: Option<usize>,
78 _phantom: std::marker::PhantomData<S>,
79}
80
81impl<S, PF, MF> FunctionalPartitioner<S, PF, MF>
82where
83 S: PlanningSolution,
84 PF: Fn(&S) -> Vec<S> + Send + Sync,
85 MF: Fn(&S, Vec<S>) -> S + Send + Sync,
86{
87 pub fn new(partition_fn: PF, merge_fn: MF) -> Self {
89 Self {
90 partition_fn,
91 merge_fn,
92 recommended_count: None,
93 _phantom: std::marker::PhantomData,
94 }
95 }
96
97 pub fn with_recommended_count(mut self, count: usize) -> Self {
99 self.recommended_count = Some(count);
100 self
101 }
102}
103
104impl<S, PF, MF> Debug for FunctionalPartitioner<S, PF, MF>
105where
106 S: PlanningSolution,
107 PF: Fn(&S) -> Vec<S> + Send + Sync,
108 MF: Fn(&S, Vec<S>) -> S + Send + Sync,
109{
110 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111 f.debug_struct("FunctionalPartitioner")
112 .field("recommended_count", &self.recommended_count)
113 .finish()
114 }
115}
116
117impl<S, PF, MF> SolutionPartitioner<S> for FunctionalPartitioner<S, PF, MF>
118where
119 S: PlanningSolution,
120 PF: Fn(&S) -> Vec<S> + Send + Sync,
121 MF: Fn(&S, Vec<S>) -> S + Send + Sync,
122{
123 fn partition(&self, solution: &S) -> Vec<S> {
124 (self.partition_fn)(solution)
125 }
126
127 fn merge(&self, original: &S, partitions: Vec<S>) -> S {
128 (self.merge_fn)(original, partitions)
129 }
130
131 fn recommended_partition_count(&self) -> Option<usize> {
132 self.recommended_count
133 }
134}
135
136#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
138pub enum ThreadCount {
139 #[default]
141 Auto,
142 Unlimited,
144 Specific(usize),
146}
147
148impl ThreadCount {
149 pub fn resolve(&self, partition_count: usize) -> usize {
159 match self {
160 ThreadCount::Auto => {
161 let cpus = std::thread::available_parallelism()
162 .map(|p| p.get())
163 .unwrap_or(1);
164 std::cmp::min(cpus, partition_count)
165 }
166 ThreadCount::Unlimited => std::thread::available_parallelism()
167 .map(|p| p.get())
168 .unwrap_or(1),
169 ThreadCount::Specific(n) => std::cmp::min(*n, partition_count),
170 }
171 }
172}
173
174impl std::fmt::Display for ThreadCount {
175 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
176 match self {
177 ThreadCount::Auto => write!(f, "Auto"),
178 ThreadCount::Unlimited => write!(f, "Unlimited"),
179 ThreadCount::Specific(n) => write!(f, "{}", n),
180 }
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187 use solverforge_core::score::SimpleScore;
188
189 #[derive(Clone, Debug)]
190 struct TestSolution {
191 values: Vec<i32>,
192 score: Option<SimpleScore>,
193 }
194
195 impl PlanningSolution for TestSolution {
196 type Score = SimpleScore;
197
198 fn score(&self) -> Option<Self::Score> {
199 self.score
200 }
201
202 fn set_score(&mut self, score: Option<Self::Score>) {
203 self.score = score;
204 }
205 }
206
207 #[test]
208 fn test_thread_count_default() {
209 assert_eq!(ThreadCount::default(), ThreadCount::Auto);
210 }
211
212 #[test]
213 fn test_thread_count_display() {
214 assert_eq!(format!("{}", ThreadCount::Auto), "Auto");
215 assert_eq!(format!("{}", ThreadCount::Unlimited), "Unlimited");
216 assert_eq!(format!("{}", ThreadCount::Specific(4)), "4");
217 }
218
219 #[test]
220 fn test_thread_count_resolve_specific() {
221 assert_eq!(ThreadCount::Specific(4).resolve(10), 4);
222 assert_eq!(ThreadCount::Specific(10).resolve(4), 4); }
224
225 #[test]
226 fn test_thread_count_resolve_auto() {
227 let count = ThreadCount::Auto.resolve(100);
228 assert!(count > 0);
229 }
230
231 #[test]
232 fn test_functional_partitioner() {
233 let partitioner = FunctionalPartitioner::new(
234 |s: &TestSolution| {
235 let mid = s.values.len() / 2;
237 vec![
238 TestSolution {
239 values: s.values[..mid].to_vec(),
240 score: None,
241 },
242 TestSolution {
243 values: s.values[mid..].to_vec(),
244 score: None,
245 },
246 ]
247 },
248 |_original, partitions| {
249 let mut values = Vec::new();
251 for p in partitions {
252 values.extend(p.values);
253 }
254 TestSolution {
255 values,
256 score: None,
257 }
258 },
259 );
260
261 let solution = TestSolution {
262 values: vec![1, 2, 3, 4],
263 score: None,
264 };
265
266 let partitions = partitioner.partition(&solution);
267 assert_eq!(partitions.len(), 2);
268 assert_eq!(partitions[0].values, vec![1, 2]);
269 assert_eq!(partitions[1].values, vec![3, 4]);
270
271 let merged = partitioner.merge(&solution, partitions);
272 assert_eq!(merged.values, vec![1, 2, 3, 4]);
273 }
274
275 #[test]
276 fn test_partitioner_debug() {
277 let partitioner = FunctionalPartitioner::new(
278 |_: &TestSolution| Vec::new(),
279 |original: &TestSolution, _| original.clone(),
280 )
281 .with_recommended_count(4);
282
283 let debug = format!("{:?}", partitioner);
284 assert!(debug.contains("FunctionalPartitioner"));
285 assert!(debug.contains("recommended_count"));
286 }
287}