solverforge_solver/manager/builder.rs
1//! Builder for SolverManager configuration.
2//!
3//! This module provides the builder pattern for configuring a [`SolverManager`].
4//! The builder allows fluent configuration of:
5//!
6//! - Construction heuristic phases
7//! - Local search phases with various acceptors
8//! - Termination conditions (time limits, step limits)
9//!
10//! # Example
11//!
12//! ```
13//! use solverforge_solver::manager::{SolverManagerBuilder, LocalSearchType, ConstructionType};
14//! use solverforge_core::domain::PlanningSolution;
15//! use solverforge_core::score::SimpleScore;
16//! use std::time::Duration;
17//!
18//! #[derive(Clone)]
19//! struct Schedule { score: Option<SimpleScore> }
20//!
21//! impl PlanningSolution for Schedule {
22//! type Score = SimpleScore;
23//! fn score(&self) -> Option<Self::Score> { self.score }
24//! fn set_score(&mut self, score: Option<Self::Score>) { self.score = score; }
25//! }
26//!
27//! let manager = SolverManagerBuilder::new(|_: &Schedule| SimpleScore::of(0))
28//! .with_construction_heuristic()
29//! .with_local_search(LocalSearchType::HillClimbing)
30//! .with_time_limit(Duration::from_secs(60))
31//! .build()
32//! .unwrap();
33//! ```
34
35use std::time::Duration;
36
37use solverforge_core::domain::PlanningSolution;
38use solverforge_core::SolverForgeError;
39
40use crate::termination::{
41 OrCompositeTermination, StepCountTermination, Termination, TimeTermination,
42};
43
44use super::config::{ConstructionType, LocalSearchType, PhaseConfig};
45use super::{SolverManager, SolverPhaseFactory};
46
47/// Builder for creating a [`SolverManager`] with fluent configuration.
48///
49/// The builder pattern allows configuring phases, termination conditions,
50/// and other solver settings before creating the manager.
51///
52/// # Basic Usage
53///
54/// ```
55/// use solverforge_solver::manager::{SolverManagerBuilder, LocalSearchType};
56/// use solverforge_core::domain::PlanningSolution;
57/// use solverforge_core::score::SimpleScore;
58/// use std::time::Duration;
59///
60/// #[derive(Clone)]
61/// struct Problem { value: i64, score: Option<SimpleScore> }
62///
63/// impl PlanningSolution for Problem {
64/// type Score = SimpleScore;
65/// fn score(&self) -> Option<Self::Score> { self.score }
66/// fn set_score(&mut self, score: Option<Self::Score>) { self.score = score; }
67/// }
68///
69/// let manager = SolverManagerBuilder::new(|p: &Problem| SimpleScore::of(-p.value))
70/// .with_construction_heuristic()
71/// .with_local_search(LocalSearchType::HillClimbing)
72/// .with_time_limit(Duration::from_secs(30))
73/// .build()
74/// .expect("Failed to build manager");
75/// ```
76///
77/// # Configuration Options
78///
79/// The builder supports:
80/// - Construction heuristic phases (first fit, best fit)
81/// - Local search phases (hill climbing, tabu search, simulated annealing, late acceptance)
82/// - Time limits
83/// - Step limits
84///
85/// # Multi-Phase Configuration
86///
87/// ```
88/// use solverforge_solver::manager::{SolverManagerBuilder, LocalSearchType, ConstructionType};
89/// use solverforge_core::domain::PlanningSolution;
90/// use solverforge_core::score::SimpleScore;
91/// use std::time::Duration;
92///
93/// # #[derive(Clone)]
94/// # struct Problem { score: Option<SimpleScore> }
95/// # impl PlanningSolution for Problem {
96/// # type Score = SimpleScore;
97/// # fn score(&self) -> Option<Self::Score> { self.score }
98/// # fn set_score(&mut self, score: Option<Self::Score>) { self.score = score; }
99/// # }
100/// let manager = SolverManagerBuilder::new(|_: &Problem| SimpleScore::of(0))
101/// // First phase: construct initial solution
102/// .with_construction_heuristic_type(ConstructionType::BestFit)
103/// // Second phase: improve with tabu search
104/// .with_local_search_steps(LocalSearchType::TabuSearch { tabu_size: 7 }, 1000)
105/// // Third phase: fine-tune with hill climbing
106/// .with_local_search(LocalSearchType::HillClimbing)
107/// // Global termination
108/// .with_time_limit(Duration::from_secs(60))
109/// .build()
110/// .unwrap();
111/// ```
112///
113/// # Zero-Erasure Design
114///
115/// The score calculator is stored as a concrete generic type parameter `C`,
116/// not as `Arc<dyn Fn>`. This eliminates virtual dispatch overhead.
117pub struct SolverManagerBuilder<S, C>
118where
119 S: PlanningSolution,
120 C: Fn(&S) -> S::Score + Send + Sync,
121{
122 score_calculator: C,
123 phase_configs: Vec<PhaseConfig>,
124 time_limit: Option<Duration>,
125 step_limit: Option<u64>,
126 _phantom: std::marker::PhantomData<S>,
127}
128
129impl<S, C> SolverManagerBuilder<S, C>
130where
131 S: PlanningSolution,
132 C: Fn(&S) -> S::Score + Send + Sync + 'static,
133{
134 /// Creates a new builder with the given score calculator (zero-erasure).
135 ///
136 /// The score calculator is a function that computes the score for a solution.
137 /// Higher scores are better (for minimization, use negative values).
138 ///
139 /// # Example
140 ///
141 /// ```
142 /// use solverforge_solver::manager::SolverManagerBuilder;
143 /// use solverforge_core::domain::PlanningSolution;
144 /// use solverforge_core::score::SimpleScore;
145 ///
146 /// # #[derive(Clone)]
147 /// # struct Problem { cost: i64, score: Option<SimpleScore> }
148 /// # impl PlanningSolution for Problem {
149 /// # type Score = SimpleScore;
150 /// # fn score(&self) -> Option<Self::Score> { self.score }
151 /// # fn set_score(&mut self, score: Option<Self::Score>) { self.score = score; }
152 /// # }
153 /// // For minimization, negate the cost
154 /// let builder = SolverManagerBuilder::new(|p: &Problem| {
155 /// SimpleScore::of(-p.cost)
156 /// });
157 /// ```
158 pub fn new(score_calculator: C) -> Self {
159 Self {
160 score_calculator,
161 phase_configs: Vec::new(),
162 time_limit: None,
163 step_limit: None,
164 _phantom: std::marker::PhantomData,
165 }
166 }
167
168 /// Adds a construction heuristic phase with default (FirstFit) configuration.
169 ///
170 /// This phase will build an initial solution by assigning values to
171 /// uninitialized planning variables using the first valid value found.
172 ///
173 /// # Example
174 ///
175 /// ```
176 /// use solverforge_solver::manager::SolverManagerBuilder;
177 /// use solverforge_core::domain::PlanningSolution;
178 /// use solverforge_core::score::SimpleScore;
179 ///
180 /// # #[derive(Clone)]
181 /// # struct Problem { score: Option<SimpleScore> }
182 /// # impl PlanningSolution for Problem {
183 /// # type Score = SimpleScore;
184 /// # fn score(&self) -> Option<Self::Score> { self.score }
185 /// # fn set_score(&mut self, score: Option<Self::Score>) { self.score = score; }
186 /// # }
187 /// let builder = SolverManagerBuilder::new(|_: &Problem| SimpleScore::of(0))
188 /// .with_construction_heuristic();
189 /// ```
190 pub fn with_construction_heuristic(mut self) -> Self {
191 self.phase_configs.push(PhaseConfig::ConstructionHeuristic {
192 construction_type: ConstructionType::FirstFit,
193 });
194 self
195 }
196
197 /// Adds a construction heuristic phase with specific configuration.
198 ///
199 /// Use this to choose between [`ConstructionType::FirstFit`] (fast) and
200 /// [`ConstructionType::BestFit`] (better quality initial solution).
201 ///
202 /// # Example
203 ///
204 /// ```
205 /// use solverforge_solver::manager::{SolverManagerBuilder, ConstructionType};
206 /// use solverforge_core::domain::PlanningSolution;
207 /// use solverforge_core::score::SimpleScore;
208 ///
209 /// # #[derive(Clone)]
210 /// # struct Problem { score: Option<SimpleScore> }
211 /// # impl PlanningSolution for Problem {
212 /// # type Score = SimpleScore;
213 /// # fn score(&self) -> Option<Self::Score> { self.score }
214 /// # fn set_score(&mut self, score: Option<Self::Score>) { self.score = score; }
215 /// # }
216 /// let builder = SolverManagerBuilder::new(|_: &Problem| SimpleScore::of(0))
217 /// .with_construction_heuristic_type(ConstructionType::BestFit);
218 /// ```
219 pub fn with_construction_heuristic_type(mut self, construction_type: ConstructionType) -> Self {
220 self.phase_configs
221 .push(PhaseConfig::ConstructionHeuristic { construction_type });
222 self
223 }
224
225 /// Adds a local search phase.
226 ///
227 /// Local search improves an existing solution by exploring neighboring
228 /// solutions. The search type determines the acceptance criteria.
229 ///
230 /// # Example
231 ///
232 /// ```
233 /// use solverforge_solver::manager::{SolverManagerBuilder, LocalSearchType};
234 /// use solverforge_core::domain::PlanningSolution;
235 /// use solverforge_core::score::SimpleScore;
236 ///
237 /// # #[derive(Clone)]
238 /// # struct Problem { score: Option<SimpleScore> }
239 /// # impl PlanningSolution for Problem {
240 /// # type Score = SimpleScore;
241 /// # fn score(&self) -> Option<Self::Score> { self.score }
242 /// # fn set_score(&mut self, score: Option<Self::Score>) { self.score = score; }
243 /// # }
244 /// let builder = SolverManagerBuilder::new(|_: &Problem| SimpleScore::of(0))
245 /// .with_local_search(LocalSearchType::TabuSearch { tabu_size: 7 });
246 /// ```
247 pub fn with_local_search(mut self, search_type: LocalSearchType) -> Self {
248 self.phase_configs.push(PhaseConfig::LocalSearch {
249 search_type,
250 step_limit: None,
251 });
252 self
253 }
254
255 /// Adds a local search phase with a step limit.
256 ///
257 /// The phase will terminate after the specified number of steps,
258 /// allowing for multi-phase configurations where different search
259 /// strategies are used in sequence.
260 ///
261 /// # Example
262 ///
263 /// ```
264 /// use solverforge_solver::manager::{SolverManagerBuilder, LocalSearchType};
265 /// use solverforge_core::domain::PlanningSolution;
266 /// use solverforge_core::score::SimpleScore;
267 ///
268 /// # #[derive(Clone)]
269 /// # struct Problem { score: Option<SimpleScore> }
270 /// # impl PlanningSolution for Problem {
271 /// # type Score = SimpleScore;
272 /// # fn score(&self) -> Option<Self::Score> { self.score }
273 /// # fn set_score(&mut self, score: Option<Self::Score>) { self.score = score; }
274 /// # }
275 /// let builder = SolverManagerBuilder::new(|_: &Problem| SimpleScore::of(0))
276 /// // First, use simulated annealing for 500 steps
277 /// .with_local_search_steps(
278 /// LocalSearchType::SimulatedAnnealing {
279 /// starting_temp: 1.0,
280 /// decay_rate: 0.99,
281 /// },
282 /// 500,
283 /// )
284 /// // Then switch to hill climbing
285 /// .with_local_search(LocalSearchType::HillClimbing);
286 /// ```
287 pub fn with_local_search_steps(
288 mut self,
289 search_type: LocalSearchType,
290 step_limit: u64,
291 ) -> Self {
292 self.phase_configs.push(PhaseConfig::LocalSearch {
293 search_type,
294 step_limit: Some(step_limit),
295 });
296 self
297 }
298
299 /// Sets the global time limit for solving.
300 ///
301 /// The solver will terminate after this duration, regardless of
302 /// which phase is currently executing.
303 ///
304 /// # Example
305 ///
306 /// ```
307 /// use solverforge_solver::manager::SolverManagerBuilder;
308 /// use solverforge_core::domain::PlanningSolution;
309 /// use solverforge_core::score::SimpleScore;
310 /// use std::time::Duration;
311 ///
312 /// # #[derive(Clone)]
313 /// # struct Problem { score: Option<SimpleScore> }
314 /// # impl PlanningSolution for Problem {
315 /// # type Score = SimpleScore;
316 /// # fn score(&self) -> Option<Self::Score> { self.score }
317 /// # fn set_score(&mut self, score: Option<Self::Score>) { self.score = score; }
318 /// # }
319 /// let builder = SolverManagerBuilder::new(|_: &Problem| SimpleScore::of(0))
320 /// .with_time_limit(Duration::from_secs(60));
321 /// ```
322 pub fn with_time_limit(mut self, duration: Duration) -> Self {
323 self.time_limit = Some(duration);
324 self
325 }
326
327 /// Sets the global step limit for solving.
328 ///
329 /// The solver will terminate after this many steps total across all phases.
330 ///
331 /// # Example
332 ///
333 /// ```
334 /// use solverforge_solver::manager::SolverManagerBuilder;
335 /// use solverforge_core::domain::PlanningSolution;
336 /// use solverforge_core::score::SimpleScore;
337 ///
338 /// # #[derive(Clone)]
339 /// # struct Problem { score: Option<SimpleScore> }
340 /// # impl PlanningSolution for Problem {
341 /// # type Score = SimpleScore;
342 /// # fn score(&self) -> Option<Self::Score> { self.score }
343 /// # fn set_score(&mut self, score: Option<Self::Score>) { self.score = score; }
344 /// # }
345 /// let builder = SolverManagerBuilder::new(|_: &Problem| SimpleScore::of(0))
346 /// .with_step_limit(10000);
347 /// ```
348 pub fn with_step_limit(mut self, steps: u64) -> Self {
349 self.step_limit = Some(steps);
350 self
351 }
352
353 /// Builds the [`SolverManager`].
354 ///
355 /// This creates a basic `SolverManager` with the configured termination
356 /// conditions. For full functionality with phases, use the typed phase
357 /// factories from [`super::phase_factory`].
358 ///
359 /// # Errors
360 ///
361 /// Currently this method always succeeds, but returns a `Result` for
362 /// forward compatibility with validation.
363 ///
364 /// # Example
365 ///
366 /// ```
367 /// use solverforge_solver::manager::{SolverManagerBuilder, LocalSearchType};
368 /// use solverforge_core::domain::PlanningSolution;
369 /// use solverforge_core::score::SimpleScore;
370 /// use std::time::Duration;
371 ///
372 /// # #[derive(Clone)]
373 /// # struct Problem { score: Option<SimpleScore> }
374 /// # impl PlanningSolution for Problem {
375 /// # type Score = SimpleScore;
376 /// # fn score(&self) -> Option<Self::Score> { self.score }
377 /// # fn set_score(&mut self, score: Option<Self::Score>) { self.score = score; }
378 /// # }
379 /// let manager = SolverManagerBuilder::new(|_: &Problem| SimpleScore::of(0))
380 /// .with_construction_heuristic()
381 /// .with_local_search(LocalSearchType::HillClimbing)
382 /// .with_time_limit(Duration::from_secs(30))
383 /// .build()
384 /// .expect("Failed to build manager");
385 ///
386 /// // Manager is ready to create solvers
387 /// let solver = manager.create_solver();
388 /// ```
389 pub fn build(self) -> Result<SolverManager<S, C>, SolverForgeError> {
390 // Build termination factory
391 let termination_factory = self.build_termination_factory();
392
393 // For now, phase factories are empty - users need to add phases manually
394 // or use the typed phase constructors. Full auto-configuration requires
395 // macro enhancements to generate the necessary metadata.
396 let phase_factories: Vec<Box<dyn SolverPhaseFactory<S>>> = Vec::new();
397
398 // Store phase configs for future use
399 // (will be used when PhaseFactory auto-configuration is implemented)
400 let _ = self.phase_configs;
401
402 Ok(SolverManager::new(
403 self.score_calculator,
404 phase_factories,
405 termination_factory,
406 ))
407 }
408
409 #[allow(clippy::type_complexity)]
410 fn build_termination_factory(
411 &self,
412 ) -> Option<Box<dyn Fn() -> Box<dyn Termination<S>> + Send + Sync>> {
413 let time_limit = self.time_limit;
414 let step_limit = self.step_limit;
415
416 if time_limit.is_none() && step_limit.is_none() {
417 return None;
418 }
419
420 Some(Box::new(move || {
421 let mut terminations: Vec<Box<dyn Termination<S>>> = Vec::new();
422
423 if let Some(duration) = time_limit {
424 terminations.push(Box::new(TimeTermination::new(duration)));
425 }
426
427 if let Some(steps) = step_limit {
428 terminations.push(Box::new(StepCountTermination::new(steps)));
429 }
430
431 match terminations.len() {
432 0 => unreachable!(),
433 1 => terminations.remove(0),
434 _ => Box::new(OrCompositeTermination::new(terminations)),
435 }
436 }))
437 }
438}