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