1use std::sync::atomic::AtomicBool;
13use std::time::Duration;
14
15use solverforge_config::{PhaseConfig, SolverConfig};
16use solverforge_core::domain::PlanningSolution;
17use solverforge_core::score::{ParseableScore, Score};
18use solverforge_scoring::{ConstraintSet, ScoreDirector};
19use tracing::info;
20
21use crate::builder::basic_selector::BasicLeafSelector;
22use crate::builder::{
23 AcceptorBuilder, AnyAcceptor, BasicContext, BasicMoveSelectorBuilder, ForagerBuilder,
24};
25use crate::heuristic::r#move::EitherMove;
26use crate::heuristic::selector::decorator::UnionMoveSelector;
27use crate::heuristic::selector::decorator::VecUnionSelector;
28use crate::heuristic::selector::{
29 EitherChangeMoveSelector, EitherSwapMoveSelector, FromSolutionEntitySelector,
30 StaticTypedValueSelector,
31};
32use crate::phase::construction::{BestFitForager, ConstructionHeuristicPhase, QueuedEntityPlacer};
33use crate::phase::localsearch::{LocalSearchPhase, SimulatedAnnealingAcceptor};
34use crate::problem_spec::ProblemSpec;
35use crate::run::AnyTermination;
36use crate::solver::{SolveResult, Solver};
37
38type ConfigLocalSearch<S> = LocalSearchPhase<
40 S,
41 EitherMove<S, usize>,
42 VecUnionSelector<S, EitherMove<S, usize>, BasicLeafSelector<S>>,
43 AnyAcceptor<S>,
44 crate::builder::AnyForager<S>,
45>;
46
47type DefaultLocalSearch<S> = LocalSearchPhase<
49 S,
50 EitherMove<S, usize>,
51 UnionMoveSelector<
52 S,
53 EitherMove<S, usize>,
54 EitherChangeMoveSelector<
55 S,
56 usize,
57 FromSolutionEntitySelector,
58 StaticTypedValueSelector<S, usize>,
59 >,
60 EitherSwapMoveSelector<S, usize, FromSolutionEntitySelector, FromSolutionEntitySelector>,
61 >,
62 SimulatedAnnealingAcceptor,
63 crate::phase::localsearch::AcceptedCountForager<S>,
64>;
65
66enum BasicLocalSearch<S: PlanningSolution>
68where
69 S::Score: Score,
70{
71 Default(DefaultLocalSearch<S>),
72 Config(ConfigLocalSearch<S>),
73}
74
75pub struct BasicSpec<S> {
80 pub get_variable: fn(&S, usize) -> Option<usize>,
81 pub set_variable: fn(&mut S, usize, Option<usize>),
82 pub value_count: fn(&S) -> usize,
83 pub entity_count_fn: fn(&S) -> usize,
84 pub variable_field: &'static str,
85 pub descriptor_index: usize,
86}
87
88impl<S, C> ProblemSpec<S, C> for BasicSpec<S>
89where
90 S: PlanningSolution,
91 S::Score: Score + ParseableScore,
92 C: ConstraintSet<S, S::Score>,
93{
94 fn is_trivial(&self, solution: &S) -> bool {
95 (self.entity_count_fn)(solution) == 0 || (self.value_count)(solution) == 0
96 }
97
98 fn default_time_limit_secs(&self) -> u64 {
99 30
100 }
101
102 fn log_scale(&self, solution: &S) {
103 info!(
104 event = "solve_start",
105 entity_count = (self.entity_count_fn)(solution),
106 value_count = (self.value_count)(solution),
107 );
108 }
109
110 fn build_and_solve(
111 self,
112 director: ScoreDirector<S, C>,
113 config: &SolverConfig,
114 time_limit: Duration,
115 termination: AnyTermination<S, ScoreDirector<S, C>>,
116 terminate: Option<&AtomicBool>,
117 callback: impl Fn(&S) + Send + Sync,
118 ) -> SolveResult<S> {
119 let n_values = (self.value_count)(director.working_solution());
120 let values: Vec<usize> = (0..n_values).collect();
121 let entity_selector = FromSolutionEntitySelector::new(0);
122 let value_selector = StaticTypedValueSelector::new(values.clone());
123 let placer = QueuedEntityPlacer::new(
124 entity_selector,
125 value_selector,
126 self.get_variable,
127 self.set_variable,
128 0,
129 self.variable_field,
130 );
131 let construction = ConstructionHeuristicPhase::new(placer, BestFitForager::new());
132
133 let local_search = build_local_search::<S>(
134 config,
135 self.get_variable,
136 self.set_variable,
137 values,
138 self.variable_field,
139 self.descriptor_index,
140 );
141
142 match local_search {
143 BasicLocalSearch::Default(ls) => {
144 let solver = Solver::new(((), construction, ls))
145 .with_termination(termination)
146 .with_time_limit(time_limit)
147 .with_best_solution_callback(callback);
148 if let Some(flag) = terminate {
149 solver.with_terminate(flag).solve(director)
150 } else {
151 solver.solve(director)
152 }
153 }
154 BasicLocalSearch::Config(ls) => {
155 let solver = Solver::new(((), construction, ls))
156 .with_termination(termination)
157 .with_time_limit(time_limit)
158 .with_best_solution_callback(callback);
159 if let Some(flag) = terminate {
160 solver.with_terminate(flag).solve(director)
161 } else {
162 solver.solve(director)
163 }
164 }
165 }
166 }
167}
168
169fn build_local_search<S>(
171 config: &SolverConfig,
172 get_variable: fn(&S, usize) -> Option<usize>,
173 set_variable: fn(&mut S, usize, Option<usize>),
174 values: Vec<usize>,
175 variable_field: &'static str,
176 descriptor_index: usize,
177) -> BasicLocalSearch<S>
178where
179 S: PlanningSolution,
180 S::Score: Score,
181{
182 let ls_config = config.phases.iter().find_map(|p| {
184 if let PhaseConfig::LocalSearch(ls) = p {
185 Some(ls)
186 } else {
187 None
188 }
189 });
190
191 let Some(ls) = ls_config else {
192 let change_selector = EitherChangeMoveSelector::simple(
194 get_variable,
195 set_variable,
196 descriptor_index,
197 variable_field,
198 values,
199 );
200 let swap_selector = EitherSwapMoveSelector::simple(
201 get_variable,
202 set_variable,
203 descriptor_index,
204 variable_field,
205 );
206 let move_selector = UnionMoveSelector::new(change_selector, swap_selector);
207 let acceptor = SimulatedAnnealingAcceptor::default();
208 let forager = crate::phase::localsearch::AcceptedCountForager::new(1);
209 return BasicLocalSearch::Default(LocalSearchPhase::new(
210 move_selector,
211 acceptor,
212 forager,
213 None,
214 ));
215 };
216
217 let acceptor = ls
219 .acceptor
220 .as_ref()
221 .map(|ac| AcceptorBuilder::build::<S>(ac))
222 .unwrap_or_else(|| AnyAcceptor::SimulatedAnnealing(SimulatedAnnealingAcceptor::default()));
223
224 let forager = ForagerBuilder::build::<S>(ls.forager.as_ref());
225
226 let ctx = BasicContext {
227 get_variable,
228 set_variable,
229 values,
230 descriptor_index,
231 variable_field,
232 };
233
234 let move_selector = BasicMoveSelectorBuilder::build(ls.move_selector.as_ref(), &ctx);
235
236 BasicLocalSearch::Config(LocalSearchPhase::new(
237 move_selector,
238 acceptor,
239 forager,
240 None,
241 ))
242}