1use std::sync::atomic::AtomicBool;
13use std::time::Duration;
14
15use solverforge_config::SolverConfig;
16use solverforge_core::domain::{PlanningSolution, SolutionDescriptor};
17use solverforge_core::score::Score;
18use solverforge_scoring::{ConstraintSet, TypedScoreDirector};
19use tokio::sync::mpsc;
20use tracing::info;
21
22use crate::phase::basic::{BasicConstructionPhase, BasicLocalSearchPhase};
23use crate::scope::SolverScope;
24use crate::solver::Solver;
25use crate::termination::{
26 OrTermination, StepCountTermination, TimeTermination, UnimprovedStepCountTermination,
27 UnimprovedTimeTermination,
28};
29
30const DEFAULT_TIME_LIMIT_SECS: u64 = 30;
32
33#[allow(clippy::too_many_arguments)]
57pub fn run_solver<S, C>(
58 solution: S,
59 finalize_fn: fn(&mut S),
60 constraints_fn: fn() -> C,
61 get_variable: fn(&S, usize) -> Option<usize>,
62 set_variable: fn(&mut S, usize, Option<usize>),
63 value_count: fn(&S) -> usize,
64 entity_count_fn: fn(&S) -> usize,
65 _descriptor: fn() -> SolutionDescriptor,
66 _entity_count: fn(&S, usize) -> usize,
67 _variable_field: &'static str,
68 _descriptor_index: usize,
69) -> S
70where
71 S: PlanningSolution,
72 S::Score: Score,
73 C: ConstraintSet<S, S::Score>,
74{
75 let (sender, _receiver) = mpsc::unbounded_channel();
77 run_solver_with_channel(
78 solution,
79 finalize_fn,
80 constraints_fn,
81 get_variable,
82 set_variable,
83 value_count,
84 entity_count_fn,
85 None,
86 sender,
87 )
88}
89
90#[allow(clippy::too_many_arguments)]
95pub fn run_solver_with_channel<S, C>(
96 mut solution: S,
97 finalize_fn: fn(&mut S),
98 constraints_fn: fn() -> C,
99 get_variable: fn(&S, usize) -> Option<usize>,
100 set_variable: fn(&mut S, usize, Option<usize>),
101 value_count: fn(&S) -> usize,
102 entity_count_fn: fn(&S) -> usize,
103 terminate: Option<&AtomicBool>,
104 sender: mpsc::UnboundedSender<(S, S::Score)>,
105) -> S
106where
107 S: PlanningSolution,
108 S::Score: Score,
109 C: ConstraintSet<S, S::Score>,
110{
111 finalize_fn(&mut solution);
112
113 let config = SolverConfig::load("solver.toml").unwrap_or_default();
114 let n_entities = entity_count_fn(&solution);
115 let n_values = value_count(&solution);
116
117 info!(
118 event = "solve_start",
119 entity_count = n_entities,
120 value_count = n_values,
121 );
122
123 let constraints = constraints_fn();
125 let director = TypedScoreDirector::new(solution, constraints);
126
127 if n_entities == 0 || n_values == 0 {
129 let mut solver_scope = SolverScope::new(director);
130 solver_scope.start_solving();
131 let score = solver_scope.calculate_score();
132 info!(event = "solve_end", score = %score);
133 return solver_scope.take_best_or_working_solution();
134 }
135
136 let construction =
138 BasicConstructionPhase::new(get_variable, set_variable, entity_count_fn, value_count);
139
140 let local_search = BasicLocalSearchPhase::new(
141 get_variable,
142 set_variable,
143 entity_count_fn,
144 value_count,
145 sender,
146 );
147
148 let result = solve_with_termination(
150 director,
151 construction,
152 local_search,
153 terminate,
154 config.termination.as_ref(),
155 );
156
157 let score = result.score().unwrap_or_default();
158 info!(event = "solve_end", score = %score);
159 result
160}
161
162fn solve_with_termination<S, D, G, T, E, V>(
163 director: D,
164 construction: BasicConstructionPhase<S, G, T, E, V>,
165 local_search: BasicLocalSearchPhase<S, G, T, E, V>,
166 terminate: Option<&AtomicBool>,
167 term_config: Option<&solverforge_config::TerminationConfig>,
168) -> S
169where
170 S: PlanningSolution,
171 S::Score: Score,
172 D: solverforge_scoring::ScoreDirector<S>,
173 G: Fn(&S, usize) -> Option<usize> + Send,
174 T: Fn(&mut S, usize, Option<usize>) + Send,
175 E: Fn(&S) -> usize + Send,
176 V: Fn(&S) -> usize + Send,
177{
178 let time_limit = term_config
179 .and_then(|c| c.time_limit())
180 .unwrap_or(Duration::from_secs(DEFAULT_TIME_LIMIT_SECS));
181 let time = TimeTermination::new(time_limit);
182
183 if let Some(step_limit) = term_config.and_then(|c| c.step_count_limit) {
185 let step = StepCountTermination::new(step_limit);
186 let termination: OrTermination<_, S, D> = OrTermination::new((time, step));
187 build_and_solve(
188 construction,
189 local_search,
190 termination,
191 terminate,
192 director,
193 time_limit,
194 )
195 } else if let Some(unimproved_step_limit) =
196 term_config.and_then(|c| c.unimproved_step_count_limit)
197 {
198 let unimproved = UnimprovedStepCountTermination::<S>::new(unimproved_step_limit);
199 let termination: OrTermination<_, S, D> = OrTermination::new((time, unimproved));
200 build_and_solve(
201 construction,
202 local_search,
203 termination,
204 terminate,
205 director,
206 time_limit,
207 )
208 } else if let Some(unimproved_time) = term_config.and_then(|c| c.unimproved_time_limit()) {
209 let unimproved = UnimprovedTimeTermination::<S>::new(unimproved_time);
210 let termination: OrTermination<_, S, D> = OrTermination::new((time, unimproved));
211 build_and_solve(
212 construction,
213 local_search,
214 termination,
215 terminate,
216 director,
217 time_limit,
218 )
219 } else {
220 let termination: OrTermination<_, S, D> = OrTermination::new((time,));
221 build_and_solve(
222 construction,
223 local_search,
224 termination,
225 terminate,
226 director,
227 time_limit,
228 )
229 }
230}
231
232fn build_and_solve<S, D, G, T, E, V, Term>(
233 construction: BasicConstructionPhase<S, G, T, E, V>,
234 local_search: BasicLocalSearchPhase<S, G, T, E, V>,
235 termination: Term,
236 terminate: Option<&AtomicBool>,
237 director: D,
238 time_limit: Duration,
239) -> S
240where
241 S: PlanningSolution,
242 S::Score: Score,
243 D: solverforge_scoring::ScoreDirector<S>,
244 G: Fn(&S, usize) -> Option<usize> + Send,
245 T: Fn(&mut S, usize, Option<usize>) + Send,
246 E: Fn(&S) -> usize + Send,
247 V: Fn(&S) -> usize + Send,
248 Term: crate::termination::Termination<S, D>,
249{
250 match terminate {
251 Some(flag) => Solver::new((construction, local_search))
252 .with_termination(termination)
253 .with_time_limit(time_limit)
254 .with_terminate(flag)
255 .solve(director),
256 None => Solver::new((construction, local_search))
257 .with_termination(termination)
258 .with_time_limit(time_limit)
259 .solve(director),
260 }
261}