rosomaxa/evolution/
config.rs

1use crate::evolution::*;
2use crate::hyper::*;
3use crate::termination::*;
4use std::hash::Hash;
5use std::sync::Arc;
6
7/// A configuration which controls evolution execution.
8pub struct EvolutionConfig<C, O, S>
9where
10    C: HeuristicContext<Objective = O, Solution = S>,
11    O: HeuristicObjective<Solution = S>,
12    S: HeuristicSolution,
13{
14    /// An initial solution config.
15    pub initial: InitialConfig<C, O, S>,
16
17    /// A pre/post processing config.
18    pub processing: ProcessingConfig<C, O, S>,
19
20    /// A heuristic context.
21    pub context: C,
22
23    /// An evolution strategy.
24    pub strategy: Box<dyn EvolutionStrategy<Context = C, Objective = O, Solution = S>>,
25
26    /// A termination defines when evolution should stop.
27    pub termination: Box<dyn Termination<Context = C, Objective = O>>,
28}
29
30/// Specifies an operator which builds initial solution.
31pub trait InitialOperator {
32    /// A heuristic context type.
33    type Context: HeuristicContext<Objective = Self::Objective, Solution = Self::Solution>;
34    /// A heuristic objective type.
35    type Objective: HeuristicObjective<Solution = Self::Solution>;
36    /// A heuristic solution type.
37    type Solution: HeuristicSolution;
38
39    /// Creates an initial solution from scratch.
40    fn create(&self, heuristic_ctx: &Self::Context) -> Self::Solution;
41}
42
43/// A collection of initial operators.
44pub type InitialOperators<C, O, S> =
45    Vec<(Box<dyn InitialOperator<Context = C, Objective = O, Solution = S> + Send + Sync>, usize)>;
46
47/// An initial solutions configuration.
48pub struct InitialConfig<C, O, S>
49where
50    C: HeuristicContext<Objective = O, Solution = S>,
51    O: HeuristicObjective<Solution = S>,
52    S: HeuristicSolution,
53{
54    /// Create methods to produce initial individuals.
55    pub operators: InitialOperators<C, O, S>,
56    /// Initial size of population to be generated.
57    pub max_size: usize,
58    /// Quota for initial solution generation.
59    pub quota: Float,
60    /// Initial individuals in population.
61    pub individuals: Vec<S>,
62}
63
64/// Specifies pre/post processing logic which is run before and after the solver.
65pub struct ProcessingConfig<C, O, S>
66where
67    C: HeuristicContext<Objective = O, Solution = S>,
68    O: HeuristicObjective<Solution = S>,
69    S: HeuristicSolution,
70{
71    /// A heuristic context creating pre processing.
72    pub context: Vec<Box<dyn HeuristicContextProcessing<Context = C, Objective = O, Solution = S> + Send + Sync>>,
73    /// A solution post processing.
74    pub solution: Vec<Box<dyn HeuristicSolutionProcessing<Solution = S> + Send + Sync>>,
75}
76
77/// Provides configurable way to build evolution configuration using fluent interface style.
78pub struct EvolutionConfigBuilder<C, O, S, K>
79where
80    C: HeuristicContext<Objective = O, Solution = S> + Stateful<Key = K> + 'static,
81    O: HeuristicObjective<Solution = S> + 'static,
82    S: HeuristicSolution + 'static,
83    K: Hash + Eq + Clone + Send + Sync + 'static,
84{
85    max_generations: Option<usize>,
86    max_time: Option<usize>,
87    min_cv: Option<(String, usize, Float, bool, K)>,
88    target_proximity: Option<(Vec<Float>, Float)>,
89    heuristic: Option<Box<dyn HyperHeuristic<Context = C, Objective = O, Solution = S>>>,
90    context: Option<C>,
91    termination: Option<Box<dyn Termination<Context = C, Objective = O>>>,
92    strategy: Option<Box<dyn EvolutionStrategy<Context = C, Objective = O, Solution = S>>>,
93
94    search_operators: Option<HeuristicSearchOperators<C, O, S>>,
95    diversify_operators: Option<HeuristicDiversifyOperators<C, O, S>>,
96
97    objective: Option<Arc<dyn HeuristicObjective<Solution = S>>>,
98
99    initial: InitialConfig<C, O, S>,
100    processing: ProcessingConfig<C, O, S>,
101}
102
103impl<C, O, S, K> Default for EvolutionConfigBuilder<C, O, S, K>
104where
105    C: HeuristicContext<Objective = O, Solution = S> + Stateful<Key = K> + 'static,
106    O: HeuristicObjective<Solution = S> + 'static,
107    S: HeuristicSolution + 'static,
108    K: Hash + Eq + Clone + Send + Sync + 'static,
109{
110    fn default() -> Self {
111        Self {
112            max_generations: None,
113            max_time: None,
114            min_cv: None,
115            target_proximity: None,
116            heuristic: None,
117            context: None,
118            termination: None,
119            strategy: None,
120            search_operators: None,
121            diversify_operators: None,
122            objective: None,
123            initial: InitialConfig { operators: vec![], max_size: 4, quota: 0.05, individuals: vec![] },
124            processing: ProcessingConfig { context: vec![], solution: vec![] },
125        }
126    }
127}
128
129impl<C, O, S, K> EvolutionConfigBuilder<C, O, S, K>
130where
131    C: HeuristicContext<Objective = O, Solution = S> + Stateful<Key = K> + 'static,
132    O: HeuristicObjective<Solution = S> + 'static,
133    S: HeuristicSolution + 'static,
134    K: Hash + Eq + Clone + Send + Sync + 'static,
135{
136    /// Sets max generations to be run by evolution. Default is 3000.
137    pub fn with_max_generations(mut self, limit: Option<usize>) -> Self {
138        self.max_generations = limit;
139        self
140    }
141
142    /// Sets max running time limit for evolution. Default is 300 seconds.
143    pub fn with_max_time(mut self, limit: Option<usize>) -> Self {
144        self.max_time = limit;
145        self
146    }
147
148    /// Sets variation coefficient termination criteria. Default is None.
149    pub fn with_min_cv(mut self, min_cv: Option<(String, usize, Float, bool)>, key: K) -> Self {
150        self.min_cv = min_cv.map(|min_cv| (min_cv.0, min_cv.1, min_cv.2, min_cv.3, key));
151        self
152    }
153
154    /// Sets target fitness and distance threshold as termination criteria.
155    pub fn with_target_proximity(mut self, target_proximity: Option<(Vec<Float>, Float)>) -> Self {
156        self.target_proximity = target_proximity;
157        self
158    }
159
160    /// Sets initial parameters used to construct initial population.
161    pub fn with_initial(mut self, max_size: usize, quota: Float, operators: InitialOperators<C, O, S>) -> Self {
162        self.initial.max_size = max_size;
163        self.initial.quota = quota;
164        self.initial.operators = operators;
165
166        self
167    }
168
169    /// Specifies processing configuration.
170    pub fn with_processing(mut self, processing: ProcessingConfig<C, O, S>) -> Self {
171        self.processing = processing;
172        self
173    }
174
175    /// Sets initial solutions in population. Default is no solutions in population.
176    pub fn with_init_solutions(mut self, solutions: Vec<S>, max_init_size: Option<usize>) -> Self {
177        if let Some(max_size) = max_init_size {
178            self.initial.max_size = max_size;
179        }
180        self.initial.individuals = solutions;
181
182        self
183    }
184
185    /// Sets objective.
186    pub fn with_objective(mut self, objective: Arc<dyn HeuristicObjective<Solution = S>>) -> Self {
187        self.objective = Some(objective);
188        self
189    }
190
191    /// Sets heuristic context.
192    pub fn with_context(mut self, context: C) -> Self {
193        self.context = Some(context);
194        self
195    }
196
197    /// Sets termination.
198    pub fn with_termination(mut self, termination: Box<dyn Termination<Context = C, Objective = O>>) -> Self {
199        self.termination = Some(termination);
200        self
201    }
202
203    /// Sets a different heuristic replacing initial.
204    pub fn with_heuristic(
205        mut self,
206        heuristic: Box<dyn HyperHeuristic<Context = C, Objective = O, Solution = S>>,
207    ) -> Self {
208        self.heuristic = Some(heuristic);
209        self
210    }
211
212    /// Sets a different heuristic replacing initial.
213    pub fn with_strategy(
214        mut self,
215        strategy: Box<dyn EvolutionStrategy<Context = C, Objective = O, Solution = S>>,
216    ) -> Self {
217        self.strategy = Some(strategy);
218        self
219    }
220
221    /// Sets search operators for dynamic heuristic.
222    pub fn with_search_operators(mut self, search_operators: HeuristicSearchOperators<C, O, S>) -> Self {
223        self.search_operators = Some(search_operators);
224        self
225    }
226
227    /// Sets diversify operators for dynamic heuristic.
228    pub fn with_diversify_operators(mut self, diversify_operators: HeuristicDiversifyOperators<C, O, S>) -> Self {
229        self.diversify_operators = Some(diversify_operators);
230        self
231    }
232
233    /// Gets termination criterias.
234    #[allow(clippy::type_complexity)]
235    fn get_termination(
236        logger: &InfoLogger,
237        max_generations: Option<usize>,
238        max_time: Option<usize>,
239        min_cv: Option<(String, usize, Float, bool, K)>,
240        target_proximity: Option<(Vec<Float>, Float)>,
241    ) -> Result<Box<dyn Termination<Context = C, Objective = O>>, GenericError> {
242        let terminations: Vec<Box<dyn Termination<Context = C, Objective = O>>> = match (
243            max_generations,
244            max_time,
245            &min_cv,
246            &target_proximity,
247        ) {
248            (None, None, None, None) => {
249                (logger)("configured to use default max-generations (3000) and max-time (300secs)");
250                vec![Box::new(MaxGeneration::new(3000)), Box::new(MaxTime::new(300.))]
251            }
252            _ => {
253                let mut terminations: Vec<Box<dyn Termination<Context = C, Objective = O>>> = vec![];
254
255                if let Some(limit) = max_generations {
256                    (logger)(format!("configured to use max-generations: {limit}").as_str());
257                    terminations.push(Box::new(MaxGeneration::new(limit)))
258                }
259
260                if let Some(limit) = max_time {
261                    (logger)(format!("configured to use max-time: {limit}s").as_str());
262                    terminations.push(Box::new(MaxTime::new(limit as Float)));
263                }
264
265                if let Some((interval_type, value, threshold, is_global, key)) = min_cv.clone() {
266                    (logger)(
267                            format!(
268                                "configured to use variation coefficient {interval_type} with sample: {value}, threshold: {threshold}",
269                            )
270                            .as_str(),
271                        );
272
273                    let variation: Box<dyn Termination<Context = C, Objective = O>> = match interval_type.as_str() {
274                        "sample" => {
275                            Box::new(MinVariation::<C, O, S, K>::new_with_sample(value, threshold, is_global, key))
276                        }
277                        "period" => {
278                            Box::new(MinVariation::<C, O, S, K>::new_with_period(value, threshold, is_global, key))
279                        }
280                        _ => return Err(format!("unknown variation interval type: {interval_type}").into()),
281                    };
282
283                    terminations.push(variation)
284                }
285
286                if let Some((target_fitness, distance_threshold)) = target_proximity.clone() {
287                    (logger)(
288                            format!(
289                                "configured to use target fitness: {target_fitness:?}, distance threshold: {distance_threshold}",
290                            )
291                            .as_str(),
292                        );
293                    terminations.push(Box::new(TargetProximity::new(target_fitness, distance_threshold)));
294                }
295
296                terminations
297            }
298        };
299
300        Ok(Box::new(CompositeTermination::new(terminations)))
301    }
302
303    /// Builds the evolution config.
304    pub fn build(self) -> Result<EvolutionConfig<C, O, S>, GenericError> {
305        let context = self.context.ok_or_else(|| "missing heuristic context".to_string())?;
306        let logger = context.environment().logger.clone();
307        let termination =
308            Self::get_termination(&logger, self.max_generations, self.max_time, self.min_cv, self.target_proximity)?;
309
310        Ok(EvolutionConfig {
311            initial: self.initial,
312            strategy: if let Some(strategy) = self.strategy {
313                (logger)("configured to use a custom strategy");
314                strategy
315            } else {
316                let heuristic = if let Some(heuristic) = self.heuristic {
317                    heuristic
318                } else {
319                    Box::new(DynamicSelective::new(
320                        self.search_operators.ok_or_else(|| "missing search operators or heuristic".to_string())?,
321                        self.diversify_operators
322                            .ok_or_else(|| "missing diversify operators or heuristic".to_string())?,
323                        context.environment(),
324                    ))
325                };
326                Box::new(strategies::Iterative::new(heuristic, 1))
327            },
328            context,
329            termination,
330            processing: self.processing,
331        })
332    }
333}