metaheurustics/test_function/
mod.rs

1use ndarray::{Array1, Array2};
2use std::f64::consts::PI;
3use crate::algorithm::ObjectiveFunction;
4
5mod additional;
6pub use additional::*;
7
8mod beale;
9pub use beale::*;
10
11/// Common trait for test functions
12pub trait TestFunction: ObjectiveFunction {
13    /// Get the known global minimum value
14    fn global_minimum(&self) -> f64;
15    
16    /// Get the known global minimum position
17    fn global_minimum_position(&self) -> Array1<f64>;
18    
19    /// Get the name of the test function
20    fn name(&self) -> &'static str;
21
22    fn test_3d_surface(&self, n_points: usize) -> (Array2<f64>, Array2<f64>, Array2<f64>) {
23        let (min_bounds, max_bounds) = self.bounds();
24        let x_min = min_bounds[0];
25        let x_max = max_bounds[0];
26        let y_min = min_bounds[1];
27        let y_max = max_bounds[1];
28
29        let x_step = (x_max - x_min) / (n_points - 1) as f64;
30        let y_step = (y_max - y_min) / (n_points - 1) as f64;
31
32        let mut x = Array2::zeros((n_points, n_points));
33        let mut y = Array2::zeros((n_points, n_points));
34        let mut z = Array2::zeros((n_points, n_points));
35
36        for i in 0..n_points {
37            for j in 0..n_points {
38                let x_val = x_min + i as f64 * x_step;
39                let y_val = y_min + j as f64 * y_step;
40                x[[i, j]] = x_val;
41                y[[i, j]] = y_val;
42                z[[i, j]] = self.evaluate(&Array1::from_vec(vec![x_val, y_val]));
43            }
44        }
45
46        (x, y, z)
47    }
48}
49
50/// Ackley Function
51/// f(x) = -20exp(-0.2√(1/n∑x²)) - exp(1/n∑cos(2πx)) + 20 + e
52#[derive(Debug)]
53pub struct Ackley {
54    dims: usize,
55}
56
57impl Ackley {
58    pub fn new(dimensions: usize) -> Self {
59        Self { dims: dimensions }
60    }
61}
62
63impl ObjectiveFunction for Ackley {
64    fn evaluate(&self, x: &Array1<f64>) -> f64 {
65        let a = 20.0;
66        let b = 0.2;
67        let c = 2.0 * PI;
68        
69        let n = x.len() as f64;
70        let sum_sq = x.iter().map(|xi| xi * xi).sum::<f64>();
71        let sum_cos = x.iter().map(|xi| (c * xi).cos()).sum::<f64>();
72        
73        -a * (-b * (sum_sq / n).sqrt()).exp() - (sum_cos / n).exp() + a + std::f64::consts::E
74    }
75    
76    fn dimensions(&self) -> usize {
77        self.dims
78    }
79    
80    fn bounds(&self) -> (Vec<f64>, Vec<f64>) {
81        (
82            vec![-32.768; self.dims],
83            vec![32.768; self.dims]
84        )
85    }
86}
87
88impl TestFunction for Ackley {
89    fn global_minimum(&self) -> f64 {
90        0.0
91    }
92    
93    fn global_minimum_position(&self) -> Array1<f64> {
94        Array1::zeros(self.dims)
95    }
96    
97    fn name(&self) -> &'static str {
98        "Ackley Function"
99    }
100}
101
102/// Sphere Function (De Jong F1)
103/// f(x) = ∑x²
104#[derive(Debug)]
105pub struct Sphere {
106    dims: usize,
107}
108
109impl Sphere {
110    pub fn new(dimensions: usize) -> Self {
111        Self { dims: dimensions }
112    }
113}
114
115impl ObjectiveFunction for Sphere {
116    fn evaluate(&self, x: &Array1<f64>) -> f64 {
117        x.iter().map(|xi| xi * xi).sum()
118    }
119    
120    fn dimensions(&self) -> usize {
121        self.dims
122    }
123    
124    fn bounds(&self) -> (Vec<f64>, Vec<f64>) {
125        (
126            vec![-5.12; self.dims],
127            vec![5.12; self.dims]
128        )
129    }
130}
131
132impl TestFunction for Sphere {
133    fn global_minimum(&self) -> f64 {
134        0.0
135    }
136    
137    fn global_minimum_position(&self) -> Array1<f64> {
138        Array1::zeros(self.dims)
139    }
140    
141    fn name(&self) -> &'static str {
142        "Sphere Function"
143    }
144}
145
146/// Rosenbrock Function (De Jong F2)
147/// f(x) = ∑[100(x_{i+1} - x_i²)² + (1 - x_i)²]
148#[derive(Debug)]
149pub struct Rosenbrock {
150    dims: usize,
151}
152
153impl Rosenbrock {
154    pub fn new(dimensions: usize) -> Self {
155        if dimensions < 2 {
156            panic!("Rosenbrock function requires at least 2 dimensions");
157        }
158        Self { dims: dimensions }
159    }
160}
161
162impl ObjectiveFunction for Rosenbrock {
163    fn evaluate(&self, x: &Array1<f64>) -> f64 {
164        let mut sum = 0.0;
165        for i in 0..self.dims - 1 {
166            sum += 100.0 * (x[i + 1] - x[i].powi(2)).powi(2) + (x[i] - 1.0).powi(2);
167        }
168        sum
169    }
170    
171    fn dimensions(&self) -> usize {
172        self.dims
173    }
174    
175    fn bounds(&self) -> (Vec<f64>, Vec<f64>) {
176        (
177            vec![-2.048; self.dims],
178            vec![2.048; self.dims]
179        )
180    }
181}
182
183impl TestFunction for Rosenbrock {
184    fn global_minimum(&self) -> f64 {
185        0.0
186    }
187    
188    fn global_minimum_position(&self) -> Array1<f64> {
189        Array1::ones(self.dims)
190    }
191    
192    fn name(&self) -> &'static str {
193        "Rosenbrock Function"
194    }
195}
196
197/// Rastrigin Function
198/// f(x) = 10n + ∑[x² - 10cos(2πx)]
199#[derive(Debug)]
200pub struct Rastrigin {
201    dims: usize,
202}
203
204impl Rastrigin {
205    pub fn new(dimensions: usize) -> Self {
206        Self { dims: dimensions }
207    }
208}
209
210impl ObjectiveFunction for Rastrigin {
211    fn evaluate(&self, x: &Array1<f64>) -> f64 {
212        let a = 10.0;
213        let n = x.len() as f64;
214        let sum = x.iter()
215            .map(|xi| xi * xi - a * (2.0 * PI * xi).cos())
216            .sum::<f64>();
217        a * n + sum
218    }
219    
220    fn dimensions(&self) -> usize {
221        self.dims
222    }
223    
224    fn bounds(&self) -> (Vec<f64>, Vec<f64>) {
225        (
226            vec![-5.12; self.dims],
227            vec![5.12; self.dims]
228        )
229    }
230}
231
232impl TestFunction for Rastrigin {
233    fn global_minimum(&self) -> f64 {
234        0.0
235    }
236    
237    fn global_minimum_position(&self) -> Array1<f64> {
238        Array1::zeros(self.dims)
239    }
240    
241    fn name(&self) -> &'static str {
242        "Rastrigin Function"
243    }
244}
245
246#[cfg(test)]
247mod tests {
248    use super::*;
249    use approx::assert_relative_eq;
250    
251    #[test]
252    fn test_sphere_function() {
253        let sphere = Sphere::new(2);
254        let x = Array1::zeros(2);
255        assert_eq!(sphere.evaluate(&x), 0.0);
256        
257        let x = Array1::from_vec(vec![1.0, 1.0]);
258        assert_eq!(sphere.evaluate(&x), 2.0);
259    }
260    
261    #[test]
262    fn test_ackley_function() {
263        let ackley = Ackley::new(2);
264        let x = Array1::zeros(2);
265        assert_relative_eq!(ackley.evaluate(&x), 0.0, epsilon = 1e-10);
266    }
267    
268    #[test]
269    fn test_rosenbrock_function() {
270        let rosenbrock = Rosenbrock::new(2);
271        let x = Array1::ones(2);
272        assert_eq!(rosenbrock.evaluate(&x), 0.0);
273    }
274    
275    #[test]
276    fn test_rastrigin_function() {
277        let rastrigin = Rastrigin::new(2);
278        let x = Array1::zeros(2);
279        assert_eq!(rastrigin.evaluate(&x), 0.0);
280    }
281}