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}