1use std::sync::atomic::AtomicBool;
13use std::time::Duration;
14
15use solverforge_config::SolverConfig;
16use solverforge_core::domain::{PlanningSolution, SolutionDescriptor};
17use solverforge_core::score::{ParseableScore, Score};
18use solverforge_scoring::{ConstraintSet, TypedScoreDirector};
19use tokio::sync::mpsc;
20use tracing::info;
21
22use crate::heuristic::selector::decorator::UnionMoveSelector;
23use crate::heuristic::selector::{
24 EitherChangeMoveSelector, EitherSwapMoveSelector, FromSolutionEntitySelector,
25 StaticTypedValueSelector,
26};
27use crate::phase::construction::{BestFitForager, ConstructionHeuristicPhase, QueuedEntityPlacer};
28use crate::phase::localsearch::{
29 AcceptedCountForager, LocalSearchPhase, SimulatedAnnealingAcceptor,
30};
31use crate::scope::SolverScope;
32use crate::solver::{SolveResult, Solver};
33use crate::termination::{
34 BestScoreTermination, OrTermination, StepCountTermination, TimeTermination,
35 UnimprovedStepCountTermination, UnimprovedTimeTermination,
36};
37
38const DEFAULT_TIME_LIMIT_SECS: u64 = 30;
40
41#[allow(clippy::too_many_arguments)]
65pub fn run_solver<S, C>(
66 solution: S,
67 finalize_fn: fn(&mut S),
68 constraints_fn: fn() -> C,
69 get_variable: fn(&S, usize) -> Option<usize>,
70 set_variable: fn(&mut S, usize, Option<usize>),
71 value_count: fn(&S) -> usize,
72 entity_count_fn: fn(&S) -> usize,
73 descriptor: fn() -> SolutionDescriptor,
74 entity_count_by_descriptor: fn(&S, usize) -> usize,
75 _variable_field: &'static str,
76 _descriptor_index: usize,
77) -> S
78where
79 S: PlanningSolution,
80 S::Score: Score + ParseableScore,
81 C: ConstraintSet<S, S::Score>,
82{
83 let (sender, _receiver) = mpsc::unbounded_channel();
85 run_solver_with_channel(
86 solution,
87 finalize_fn,
88 constraints_fn,
89 get_variable,
90 set_variable,
91 value_count,
92 entity_count_fn,
93 descriptor,
94 entity_count_by_descriptor,
95 None,
96 sender,
97 )
98}
99
100#[allow(clippy::too_many_arguments)]
105pub fn run_solver_with_channel<S, C>(
106 mut solution: S,
107 finalize_fn: fn(&mut S),
108 constraints_fn: fn() -> C,
109 get_variable: fn(&S, usize) -> Option<usize>,
110 set_variable: fn(&mut S, usize, Option<usize>),
111 value_count: fn(&S) -> usize,
112 entity_count_fn: fn(&S) -> usize,
113 descriptor: fn() -> SolutionDescriptor,
114 entity_count_by_descriptor: fn(&S, usize) -> usize,
115 terminate: Option<&AtomicBool>,
116 sender: mpsc::UnboundedSender<(S, S::Score)>,
117) -> S
118where
119 S: PlanningSolution,
120 S::Score: Score + ParseableScore,
121 C: ConstraintSet<S, S::Score>,
122{
123 finalize_fn(&mut solution);
124
125 let config = SolverConfig::load("solver.toml").unwrap_or_default();
126 let n_entities = entity_count_fn(&solution);
127 let n_values = value_count(&solution);
128
129 info!(
130 event = "solve_start",
131 entity_count = n_entities,
132 value_count = n_values,
133 );
134
135 let constraints = constraints_fn();
137 let director = TypedScoreDirector::with_descriptor(
138 solution,
139 constraints,
140 descriptor(),
141 entity_count_by_descriptor,
142 );
143
144 if n_entities == 0 || n_values == 0 {
146 let mut solver_scope = SolverScope::new(director);
147 solver_scope.start_solving();
148 let score = solver_scope.calculate_score();
149 info!(event = "solve_end", score = %score);
150 let solution = solver_scope.take_best_or_working_solution();
151 let _ = sender.send((solution.clone(), score));
152 return solution;
153 }
154
155 let values: Vec<usize> = (0..n_values).collect();
157 let entity_selector = FromSolutionEntitySelector::new(0);
158 let value_selector = StaticTypedValueSelector::new(values);
159 let placer = QueuedEntityPlacer::new(
160 entity_selector,
161 value_selector,
162 get_variable,
163 set_variable,
164 0,
165 "variable",
166 );
167 let construction = ConstructionHeuristicPhase::new(placer, BestFitForager::new());
168
169 let values: Vec<usize> = (0..n_values).collect();
172 let change_selector =
173 EitherChangeMoveSelector::simple(get_variable, set_variable, 0, "variable", values);
174 let swap_selector = EitherSwapMoveSelector::simple(get_variable, set_variable, 0, "variable");
175 let move_selector = UnionMoveSelector::new(change_selector, swap_selector);
176 let acceptor = SimulatedAnnealingAcceptor::default();
177 let forager = AcceptedCountForager::new(1);
178 let local_search = LocalSearchPhase::new(move_selector, acceptor, forager, None);
179
180 let result = solve_with_termination(
182 director,
183 construction,
184 local_search,
185 terminate,
186 config.termination.as_ref(),
187 sender,
188 );
189
190 let score = result.solution.score().unwrap_or_default();
191 info!(
192 event = "solve_end",
193 score = %score,
194 steps = result.stats.step_count,
195 moves_evaluated = result.stats.moves_evaluated,
196 );
197 result.solution
198}
199
200fn solve_with_termination<S, D, M1, M2, P, Fo, MS, A, Fo2>(
201 director: D,
202 construction: ConstructionHeuristicPhase<S, M1, P, Fo>,
203 local_search: LocalSearchPhase<S, M2, MS, A, Fo2>,
204 terminate: Option<&AtomicBool>,
205 term_config: Option<&solverforge_config::TerminationConfig>,
206 sender: mpsc::UnboundedSender<(S, S::Score)>,
207) -> SolveResult<S>
208where
209 S: PlanningSolution,
210 S::Score: Score + ParseableScore,
211 D: solverforge_scoring::ScoreDirector<S>,
212 M1: crate::heuristic::r#move::Move<S>,
213 M2: crate::heuristic::r#move::Move<S>,
214 P: crate::phase::construction::EntityPlacer<S, M1>,
215 Fo: crate::phase::construction::ConstructionForager<S, M1>,
216 MS: crate::heuristic::selector::MoveSelector<S, M2>,
217 A: crate::phase::localsearch::Acceptor<S>,
218 Fo2: crate::phase::localsearch::LocalSearchForager<S, M2>,
219{
220 let time_limit = term_config
221 .and_then(|c| c.time_limit())
222 .unwrap_or(Duration::from_secs(DEFAULT_TIME_LIMIT_SECS));
223 let time = TimeTermination::new(time_limit);
224
225 let best_score_target: Option<S::Score> = term_config
227 .and_then(|c| c.best_score_limit.as_ref())
228 .and_then(|s| S::Score::parse(s).ok());
229
230 if let Some(target) = best_score_target {
232 let best_score = BestScoreTermination::new(target);
233 let termination: OrTermination<_, S, D> = OrTermination::new((time, best_score));
234 build_and_solve(
235 construction,
236 local_search,
237 termination,
238 terminate,
239 director,
240 time_limit,
241 sender,
242 )
243 } else if let Some(step_limit) = term_config.and_then(|c| c.step_count_limit) {
244 let step = StepCountTermination::new(step_limit);
245 let termination: OrTermination<_, S, D> = OrTermination::new((time, step));
246 build_and_solve(
247 construction,
248 local_search,
249 termination,
250 terminate,
251 director,
252 time_limit,
253 sender,
254 )
255 } else if let Some(unimproved_step_limit) =
256 term_config.and_then(|c| c.unimproved_step_count_limit)
257 {
258 let unimproved = UnimprovedStepCountTermination::<S>::new(unimproved_step_limit);
259 let termination: OrTermination<_, S, D> = OrTermination::new((time, unimproved));
260 build_and_solve(
261 construction,
262 local_search,
263 termination,
264 terminate,
265 director,
266 time_limit,
267 sender,
268 )
269 } else if let Some(unimproved_time) = term_config.and_then(|c| c.unimproved_time_limit()) {
270 let unimproved = UnimprovedTimeTermination::<S>::new(unimproved_time);
271 let termination: OrTermination<_, S, D> = OrTermination::new((time, unimproved));
272 build_and_solve(
273 construction,
274 local_search,
275 termination,
276 terminate,
277 director,
278 time_limit,
279 sender,
280 )
281 } else {
282 let termination: OrTermination<_, S, D> = OrTermination::new((time,));
283 build_and_solve(
284 construction,
285 local_search,
286 termination,
287 terminate,
288 director,
289 time_limit,
290 sender,
291 )
292 }
293}
294
295fn build_and_solve<S, D, M1, M2, P, Fo, MS, A, Fo2, Term>(
296 construction: ConstructionHeuristicPhase<S, M1, P, Fo>,
297 local_search: LocalSearchPhase<S, M2, MS, A, Fo2>,
298 termination: Term,
299 terminate: Option<&AtomicBool>,
300 director: D,
301 time_limit: Duration,
302 sender: mpsc::UnboundedSender<(S, S::Score)>,
303) -> SolveResult<S>
304where
305 S: PlanningSolution,
306 S::Score: Score + ParseableScore,
307 D: solverforge_scoring::ScoreDirector<S>,
308 M1: crate::heuristic::r#move::Move<S>,
309 M2: crate::heuristic::r#move::Move<S>,
310 P: crate::phase::construction::EntityPlacer<S, M1>,
311 Fo: crate::phase::construction::ConstructionForager<S, M1>,
312 MS: crate::heuristic::selector::MoveSelector<S, M2>,
313 A: crate::phase::localsearch::Acceptor<S>,
314 Fo2: crate::phase::localsearch::LocalSearchForager<S, M2>,
315 Term: crate::termination::Termination<S, D>,
316{
317 let callback_sender = sender.clone();
318 let callback: Box<dyn Fn(&S) + Send + Sync> = Box::new(move |solution: &S| {
319 let score = solution.score().unwrap_or_default();
320 let _ = callback_sender.send((solution.clone(), score));
321 });
322
323 let result = match terminate {
324 Some(flag) => Solver::new(((), construction, local_search))
325 .with_termination(termination)
326 .with_time_limit(time_limit)
327 .with_terminate(flag)
328 .with_best_solution_callback(callback)
329 .solve(director),
330 None => Solver::new(((), construction, local_search))
331 .with_termination(termination)
332 .with_time_limit(time_limit)
333 .with_best_solution_callback(callback)
334 .solve(director),
335 };
336
337 let final_score = result.solution.score().unwrap_or_default();
339 let _ = sender.send((result.solution.clone(), final_score));
340
341 result
342}