mahf/problems/evaluate.rs
1//! Evaluate [`Individual`]s according to some objective function.
2
3use std::marker::PhantomData;
4
5use better_any::{Tid, TidAble};
6use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};
7
8use crate::{CustomState, Individual, Problem, State};
9
10/// Trait for evaluating individuals, i.e. evaluate their solutions to an optimization problem.
11///
12/// Implement [`ObjectiveFunction`] instead if the objective function does not require `&mut self`
13/// to automatically implement this trait and gain default implementations for sequential
14/// and parallel evaluation through [`Sequential`] and [`Parallel`]
15/// (the latter requires the problem to be `Sync`).
16///
17/// # Examples
18///
19/// A simple implementation of the n-dimensional real-valued sphere function `f(x) = x^2`.
20///
21/// Note that implementing [`ObjectiveFunction`] would be preferred in this case, because the
22/// objective function only depends on `x`.
23///
24/// ```
25/// use mahf::{problems::Evaluate, Individual, Problem, SingleObjective, State};
26///
27/// pub struct Sphere {
28/// pub dim: usize,
29/// }
30///
31/// impl Problem for Sphere {
32/// type Encoding = Vec<f64>;
33/// type Objective = SingleObjective;
34///
35/// fn name(&self) -> &str {
36/// "Sphere"
37/// }
38/// }
39///
40/// pub struct SequentialSphereEvaluator;
41///
42/// impl Evaluate for SequentialSphereEvaluator {
43/// type Problem = Sphere;
44///
45/// /// Implements `f(x) = \sum (x_i)^2`.
46/// fn evaluate(
47/// &mut self,
48/// _problem: &Self::Problem,
49/// _state: &mut State<Self::Problem>,
50/// individuals: &mut [Individual<Self::Problem>],
51/// ) {
52/// for individual in individuals {
53/// individual.evaluate_with(|solution| {
54/// solution
55/// .iter()
56/// .map(|x| x.powi(2))
57/// .sum::<f64>()
58/// .try_into()
59/// .unwrap()
60/// })
61/// }
62/// }
63/// }
64/// ```
65///
66/// The evaluator is specified when executing the configuration:
67///
68/// ```
69/// # use mahf::{Individual, Problem, SingleObjective, State, problems::Evaluate};
70/// use mahf::prelude::*;
71///
72/// # pub struct Sphere {
73/// # pub dim: usize,
74/// # }
75/// #
76/// # impl Problem for Sphere {
77/// # type Encoding = ();
78/// # type Objective = SingleObjective;
79/// #
80/// # fn name(&self) -> &str { unimplemented!() }
81/// # }
82/// #
83/// # pub struct SequentialSphereEvaluator;
84/// #
85/// # impl SequentialSphereEvaluator {
86/// # pub fn new() -> Self {
87/// # Self
88/// # }
89/// # }
90/// #
91/// # impl Evaluate for SequentialSphereEvaluator {
92/// # type Problem = Sphere;
93/// #
94/// # fn evaluate(
95/// # &mut self,
96/// # _problem: &Self::Problem,
97/// # _state: &mut State<Self::Problem>,
98/// # individuals: &mut [Individual<Self::Problem>])
99/// # {
100/// # unimplemented!()
101/// # }
102/// # }
103/// #
104/// # fn example(config: Configuration<Sphere>, problem: Sphere) -> ExecResult<()> {
105/// // Implicit ...
106/// let state = config.optimize(&problem, SequentialSphereEvaluator::new())?;
107/// // ... or explicit insertion into the state.
108/// let state = config.optimize_with(&problem, |state| {
109/// state.insert_evaluator(SequentialSphereEvaluator::new());
110/// Ok(())
111/// })?;
112/// # Ok(())
113/// # }
114/// ```
115pub trait Evaluate: Send {
116 /// The type of optimization problem.
117 type Problem: Problem;
118
119 /// Evaluates individuals on the [`Problem`].
120 ///
121 /// [`Problem`]: Evaluate::Problem
122 fn evaluate(
123 &mut self,
124 problem: &Self::Problem,
125 state: &mut State<Self::Problem>,
126 individuals: &mut [Individual<Self::Problem>],
127 );
128}
129
130/// Trait for a non-mutable objective function of an optimization problem.
131///
132/// [`Sequential`] and [`Parallel`] provide a default implementation of sequential and parallel
133/// evaluation using the [`objective`], respectively.
134/// The latter requires the [`Problem`] to be `Sync`.
135///
136/// If your objective function takes `&mut self`, implement [`Evaluate`] directly.
137///
138/// [`objective`]: ObjectiveFunction::objective
139///
140/// # Examples
141///
142/// A simple implementation of the n-dimensional real-valued sphere function `f(x) = x^2`.
143///
144/// ```
145/// use mahf::{problems::ObjectiveFunction, Individual, Problem, SingleObjective, State};
146///
147/// pub struct Sphere {
148/// pub dim: usize,
149/// }
150///
151/// impl Problem for Sphere {
152/// type Encoding = Vec<f64>;
153/// type Objective = SingleObjective;
154///
155/// fn name(&self) -> &str {
156/// "Sphere"
157/// }
158/// }
159///
160/// impl ObjectiveFunction for Sphere {
161/// /// Implements `f(x) = \sum (x_i)^2`.
162/// fn objective(&self, solution: &Self::Encoding) -> Self::Objective {
163/// debug_assert_eq!(self.dim, solution.len());
164/// solution
165/// .iter()
166/// .map(|x| x.powi(2))
167/// .sum::<f64>()
168/// .try_into()
169/// .unwrap()
170/// }
171/// }
172/// ```
173///
174/// [`Sequential`] and [`Parallel`] can be used as evaluators:
175///
176/// ```
177/// # use mahf::{Individual, Problem, SingleObjective, State, problems::ObjectiveFunction};
178/// use mahf::prelude::*;
179/// #
180/// # pub struct Sphere {
181/// # pub dim: usize,
182/// # }
183/// #
184/// # impl Problem for Sphere {
185/// # type Encoding = Vec<f64>;
186/// # type Objective = SingleObjective;
187/// #
188/// # fn name(&self) -> &str {
189/// # "Sphere"
190/// # }
191/// # }
192/// #
193/// # impl ObjectiveFunction for Sphere {
194/// # fn objective(&self, solution: &Self::Encoding) -> Self::Objective {
195/// # unimplemented!()
196/// # }
197/// # }
198///
199/// # fn example(config: Configuration<Sphere>, problem: Sphere) -> ExecResult<()> {
200/// // Implicit insertion into the state ...
201/// let state = config.optimize(&problem, evaluate::Sequential::new())?;
202/// // ... or explicit.
203/// let state = config.optimize_with(&problem, |state| {
204/// state.insert_evaluator(evaluate::Sequential::new());
205/// Ok(())
206/// })?;
207/// # Ok(())
208/// # }
209/// ```
210pub trait ObjectiveFunction: Problem {
211 /// Calculates the objective value of the given `solution`.
212 fn objective(&self, solution: &Self::Encoding) -> Self::Objective;
213}
214
215/// A sequential evaluator for an optimization problem, i.e. [`ObjectiveFunction`].
216///
217/// The evaluator simply evaluates all individuals sequentially in order.
218#[derive(Tid)]
219pub struct Sequential<P: ObjectiveFunction + 'static>(PhantomData<fn() -> P>);
220
221impl<P: ObjectiveFunction> Sequential<P> {
222 /// Creates a new instance of a sequential evaluator for a problem `P`.
223 ///
224 /// # Examples
225 ///
226 /// ```
227 /// # use mahf::{Individual, Problem, SingleObjective, State, problems::ObjectiveFunction};
228 /// use mahf::problems::evaluate::Sequential;
229 ///
230 /// # fn example<P: ObjectiveFunction>() {
231 /// // Create a sequential evaluator for `P`.
232 /// let sequential_evaluator = Sequential::<P>::new();
233 /// # }
234 /// ```
235 pub fn new() -> Self {
236 Self(PhantomData)
237 }
238}
239
240impl<P: ObjectiveFunction> Default for Sequential<P> {
241 fn default() -> Self {
242 Self::new()
243 }
244}
245
246impl<P> Evaluate for Sequential<P>
247where
248 P: ObjectiveFunction,
249{
250 type Problem = P;
251
252 fn evaluate(
253 &mut self,
254 problem: &Self::Problem,
255 _state: &mut State<Self::Problem>,
256 individuals: &mut [Individual<Self::Problem>],
257 ) {
258 for individual in individuals {
259 individual.evaluate_with(|solution| problem.objective(solution));
260 }
261 }
262}
263
264impl<P: ObjectiveFunction> CustomState<'_> for Sequential<P> {}
265
266/// A parallel evaluator for an optimization problem.
267///
268/// Requires `P` to be `Sync`.
269///
270/// The evaluator evaluates the individuals in parallel using the [`rayon`] library.
271#[derive(Tid)]
272pub struct Parallel<P: ObjectiveFunction + 'static>(PhantomData<fn() -> P>);
273
274impl<P: ObjectiveFunction> Parallel<P> {
275 /// Creates a new instance of a parallel evaluator for a problem `P`.
276 ///
277 /// # Examples
278 ///
279 /// ```
280 /// # use mahf::{Individual, Problem, SingleObjective, State, problems::ObjectiveFunction};
281 /// use mahf::problems::evaluate::Parallel;
282 ///
283 /// # fn example<P: ObjectiveFunction>() {
284 /// // Create a sequential evaluator for `P`.
285 /// let parallel_evaluator = Parallel::<P>::new();
286 /// # }
287 /// ```
288 pub fn new() -> Self {
289 Self(PhantomData)
290 }
291}
292
293impl<P: ObjectiveFunction> Default for Parallel<P> {
294 fn default() -> Self {
295 Self::new()
296 }
297}
298
299impl<P> Evaluate for Parallel<P>
300where
301 P: ObjectiveFunction + Problem + Sync,
302{
303 type Problem = P;
304
305 fn evaluate(
306 &mut self,
307 problem: &Self::Problem,
308 _state: &mut State<Self::Problem>,
309 individuals: &mut [Individual<Self::Problem>],
310 ) {
311 individuals.par_iter_mut().for_each(|individual| {
312 individual.evaluate_with(|solution| problem.objective(solution))
313 });
314 }
315}
316
317impl<P: ObjectiveFunction> CustomState<'_> for Parallel<P> {}
318
319impl<P> Default for Box<dyn Evaluate<Problem = P>>
320where
321 P: ObjectiveFunction,
322{
323 fn default() -> Self {
324 // Use sequential evaluation by default.
325 Box::new(Sequential::<P>::new())
326 }
327}