solverforge_solver/manager/
solver_manager.rs

1//! SolverManager implementation.
2
3#![allow(clippy::type_complexity)]
4
5use solverforge_core::domain::PlanningSolution;
6
7use crate::phase::Phase;
8use crate::solver::Solver;
9use crate::termination::Termination;
10
11use super::SolverPhaseFactory;
12
13/// High-level solver manager for ergonomic solving.
14///
15/// `SolverManager` stores solver configuration and can create solvers on demand.
16/// For solving, use [`create_solver()`](Self::create_solver) to get a configured
17/// [`Solver`] instance, then provide your own `ScoreDirector`.
18///
19/// # Creating a SolverManager
20///
21/// Use the builder pattern via [`SolverManager::builder()`]:
22///
23/// ```
24/// use solverforge_solver::manager::{SolverManager, LocalSearchType};
25/// use solverforge_core::domain::PlanningSolution;
26/// use solverforge_core::score::SimpleScore;
27/// use std::time::Duration;
28///
29/// #[derive(Clone)]
30/// struct Schedule {
31///     tasks: Vec<i64>,
32///     score: Option<SimpleScore>,
33/// }
34///
35/// impl PlanningSolution for Schedule {
36///     type Score = SimpleScore;
37///     fn score(&self) -> Option<Self::Score> { self.score }
38///     fn set_score(&mut self, score: Option<Self::Score>) { self.score = score; }
39/// }
40///
41/// // Build a manager with hill climbing and 30-second time limit
42/// let manager = SolverManager::<Schedule>::builder(|s| {
43///     // Simple scoring: sum of tasks
44///     SimpleScore::of(s.tasks.iter().sum())
45/// })
46///     .with_time_limit(Duration::from_secs(30))
47///     .build()
48///     .expect("Failed to build manager");
49///
50/// // Score calculation is available without solving
51/// let schedule = Schedule { tasks: vec![1, 2, 3], score: None };
52/// let score = manager.calculate_score(&schedule);
53/// assert_eq!(score, SimpleScore::of(6));
54/// ```
55///
56/// # Creating Solvers
57///
58/// The manager creates fresh [`Solver`] instances for each solve:
59///
60/// ```no_run
61/// use solverforge_solver::manager::SolverManager;
62/// use solverforge_core::domain::PlanningSolution;
63/// use solverforge_core::score::SimpleScore;
64///
65/// # #[derive(Clone)]
66/// # struct Schedule { score: Option<SimpleScore> }
67/// # impl PlanningSolution for Schedule {
68/// #     type Score = SimpleScore;
69/// #     fn score(&self) -> Option<Self::Score> { self.score }
70/// #     fn set_score(&mut self, score: Option<Self::Score>) { self.score = score; }
71/// # }
72/// # let manager = SolverManager::<Schedule>::builder(|_| SimpleScore::of(0)).build().unwrap();
73/// // Create a solver for this problem instance
74/// let solver = manager.create_solver();
75///
76/// // Each call creates a fresh solver with clean state
77/// let solver2 = manager.create_solver();
78/// ```
79///
80/// # Zero-Erasure Design
81///
82/// The score calculator is stored as a concrete generic type parameter `C`,
83/// not as `Arc<dyn Fn>`. This eliminates virtual dispatch overhead for the
84/// hot path (score calculation is called millions of times per solve).
85///
86/// The default `C = fn(&S) -> S::Score` allows writing `SolverManager::<T>::builder(...)`
87/// without specifying the calculator type (it's inferred from the builder).
88pub struct SolverManager<S: PlanningSolution, C = fn(&S) -> <S as PlanningSolution>::Score>
89where
90    C: Fn(&S) -> S::Score + Send + Sync,
91{
92    /// Score calculator function (zero-erasure: concrete generic type).
93    score_calculator: C,
94
95    /// Configured phases (as factories that create fresh phases per solve).
96    phase_factories: Vec<Box<dyn SolverPhaseFactory<S>>>,
97
98    /// Global termination condition factory.
99    termination_factory: Option<Box<dyn Fn() -> Box<dyn Termination<S>> + Send + Sync>>,
100}
101
102impl<S: PlanningSolution> SolverManager<S, fn(&S) -> S::Score> {
103    /// Creates a new [`SolverManagerBuilder`](super::SolverManagerBuilder) with the given score calculator.
104    ///
105    /// The score calculator is a function that computes the score for a solution.
106    /// This is the entry point for building a `SolverManager`.
107    ///
108    /// # Example
109    ///
110    /// ```
111    /// use solverforge_solver::manager::SolverManager;
112    /// use solverforge_core::domain::PlanningSolution;
113    /// use solverforge_core::score::SimpleScore;
114    ///
115    /// #[derive(Clone)]
116    /// struct Problem { value: i64, score: Option<SimpleScore> }
117    ///
118    /// impl PlanningSolution for Problem {
119    ///     type Score = SimpleScore;
120    ///     fn score(&self) -> Option<Self::Score> { self.score }
121    ///     fn set_score(&mut self, score: Option<Self::Score>) { self.score = score; }
122    /// }
123    ///
124    /// let builder = SolverManager::<Problem>::builder(|p| {
125    ///     SimpleScore::of(-p.value.abs()) // Minimize absolute value
126    /// });
127    /// ```
128    pub fn builder<F>(score_calculator: F) -> super::SolverManagerBuilder<S, F>
129    where
130        F: Fn(&S) -> S::Score + Send + Sync + 'static,
131    {
132        super::SolverManagerBuilder::new(score_calculator)
133    }
134}
135
136impl<S, C> SolverManager<S, C>
137where
138    S: PlanningSolution,
139    C: Fn(&S) -> S::Score + Send + Sync,
140{
141    /// Creates a SolverManager with explicit configuration (zero-erasure).
142    pub(crate) fn new(
143        score_calculator: C,
144        phase_factories: Vec<Box<dyn SolverPhaseFactory<S>>>,
145        termination_factory: Option<Box<dyn Fn() -> Box<dyn Termination<S>> + Send + Sync>>,
146    ) -> Self {
147        Self {
148            score_calculator,
149            phase_factories,
150            termination_factory,
151        }
152    }
153
154    /// Creates a fresh [`Solver`] instance with configured phases.
155    ///
156    /// Each call returns a new solver with clean state, suitable for solving
157    /// a new problem instance. The solver is configured with termination
158    /// conditions and phases from this manager.
159    ///
160    /// # Example
161    ///
162    /// ```
163    /// use solverforge_solver::manager::SolverManager;
164    /// use solverforge_core::domain::PlanningSolution;
165    /// use solverforge_core::score::SimpleScore;
166    /// use std::time::Duration;
167    ///
168    /// # #[derive(Clone)]
169    /// # struct Problem { score: Option<SimpleScore> }
170    /// # impl PlanningSolution for Problem {
171    /// #     type Score = SimpleScore;
172    /// #     fn score(&self) -> Option<Self::Score> { self.score }
173    /// #     fn set_score(&mut self, score: Option<Self::Score>) { self.score = score; }
174    /// # }
175    /// let manager = SolverManager::<Problem>::builder(|_| SimpleScore::of(0))
176    ///     .with_step_limit(100)
177    ///     .build()
178    ///     .unwrap();
179    ///
180    /// // Create solver - each call gives a fresh instance
181    /// let solver = manager.create_solver();
182    /// ```
183    pub fn create_solver(&self) -> Solver<S> {
184        // Create fresh phases for this solve
185        let phases: Vec<Box<dyn Phase<S>>> = self
186            .phase_factories
187            .iter()
188            .map(|f| f.create_phase())
189            .collect();
190
191        // Create solver
192        let mut solver = Solver::new(phases);
193
194        // Add termination if configured
195        if let Some(factory) = &self.termination_factory {
196            solver = solver.with_termination(factory());
197        }
198
199        solver
200    }
201
202    /// Returns a reference to the score calculator function.
203    ///
204    /// # Example
205    ///
206    /// ```
207    /// use solverforge_solver::manager::SolverManager;
208    /// use solverforge_core::domain::PlanningSolution;
209    /// use solverforge_core::score::SimpleScore;
210    ///
211    /// # #[derive(Clone)]
212    /// # struct Problem { value: i64, score: Option<SimpleScore> }
213    /// # impl PlanningSolution for Problem {
214    /// #     type Score = SimpleScore;
215    /// #     fn score(&self) -> Option<Self::Score> { self.score }
216    /// #     fn set_score(&mut self, score: Option<Self::Score>) { self.score = score; }
217    /// # }
218    /// let manager = SolverManager::<Problem>::builder(|p| SimpleScore::of(p.value))
219    ///     .build()
220    ///     .unwrap();
221    ///
222    /// let calculator = manager.score_calculator();
223    /// let problem = Problem { value: 42, score: None };
224    /// let score = calculator(&problem);
225    /// assert_eq!(score, SimpleScore::of(42));
226    /// ```
227    pub fn score_calculator(&self) -> &C {
228        &self.score_calculator
229    }
230
231    /// Calculates the score for a solution using the configured calculator.
232    ///
233    /// This is a convenience method equivalent to calling the score calculator
234    /// directly.
235    ///
236    /// # Example
237    ///
238    /// ```
239    /// use solverforge_solver::manager::SolverManager;
240    /// use solverforge_core::domain::PlanningSolution;
241    /// use solverforge_core::score::SimpleScore;
242    ///
243    /// # #[derive(Clone)]
244    /// # struct Problem { value: i64, score: Option<SimpleScore> }
245    /// # impl PlanningSolution for Problem {
246    /// #     type Score = SimpleScore;
247    /// #     fn score(&self) -> Option<Self::Score> { self.score }
248    /// #     fn set_score(&mut self, score: Option<Self::Score>) { self.score = score; }
249    /// # }
250    /// let manager = SolverManager::<Problem>::builder(|p| {
251    ///     SimpleScore::of(-p.value) // Negate for minimization
252    /// })
253    ///     .build()
254    ///     .unwrap();
255    ///
256    /// let problem = Problem { value: 10, score: None };
257    /// let score = manager.calculate_score(&problem);
258    /// assert_eq!(score, SimpleScore::of(-10));
259    /// ```
260    pub fn calculate_score(&self, solution: &S) -> S::Score {
261        (self.score_calculator)(solution)
262    }
263}