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}