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}