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