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